{
  "openapi": "3.1.0",
  "info": {
    "title": "AerScheduler API",
    "version": "1.0.0",
    "summary": "Schedule aircraft, close out flights, and bill for them , from your own software.",
    "description": "The AerScheduler REST API. Schedule aircraft, close flights out, invoice them, and pull the numbers back , from your own software.\n\n## Getting started\n\n**The API is part of the Enterprise plan.** API keys can only be created, and only\nwork, for an organization on Enterprise. If **Settings → API keys** is not in your\nconsole, that is why , talk to us at https://www.aerscheduler.com/contact and we will turn it on.\n\n**The API is used with an API key**, and only with an API key.\n\nCreate one in the web console at **Settings → API keys**. Give it a name you'll\nrecognise and tick the roles it needs. The secret is shown once, at that moment,\nand is never retrievable again , we store only a hash.\n\nThe roles a key may hold are `admin`, `dispatcher`, `instructor`, `student`,\n`renter` and `technician`. `owner` cannot be granted to a key. Give a key the\nleast it needs , one that reads the schedule is a `dispatcher`, not an\n`admin` , because every permission rule that applies to a person holding those\nroles applies to the key.\n\nRevoking is in the same place, and takes effect on the very next request.\n\n**Key management is deliberately absent from the reference below**, and that is\nnot an oversight. `/apiKeys` is the one part of this API a key cannot use: a key\ncannot create, list or revoke a key, even one holding the admin role. If it\ncould, a single leaked key would be a permanent foothold , you revoke it and\nwhatever took it has already issued itself three more. So those routes answer\n403 to a key, and documenting them beside endpoints that work would be a trap.\nEverything in the reference works with the credential this page tells you to use.\n\nEverything after that , every request your software makes , uses the key:\n\n```bash\nexport AERSCHEDULER_KEY=ask_live_…\n\ncurl -s https://api.aerscheduler.com/resources/aircraft \\\n  -H \"Authorization: Bearer $AERSCHEDULER_KEY\"\n```\n\nThat is the whole setup. There is no sign-in step and no token to refresh: a key\nis a long-lived credential you store once, and it keeps working until you revoke\nit or its expiry passes.\n\nKeys carry **roles**, so a key is scoped to what it actually needs. A key that\nreads the schedule should be a `dispatcher`, not an `admin`. Every permission\nrule that applies to a person holding those roles applies to the key.\n\nCreating and revoking keys requires a signed-in administrator , a key cannot mint\nanother key, so a leaked key cannot issue itself replacements.\n\n## Conventions\n\nEvery one of these holds across the whole API, so you can write one client wrapper and stop thinking about it:\n\n- **Responses are wrapped.** A body-bearing success is `{ \"data\": … }`. An error is `{ \"message\": \"…\" }`, and the message is written to be safe to show a user. A few endpoints noted individually break this: `/health`, the CSV export, and the OAuth redirects.\n- **Status codes mean what they say.** `200` read or update, `201` created, `204` done with nothing to return, `400` your request was wrong, `401` sign in again, `403` signed in but not allowed, `404` no such thing, `429` slow down.\n- **`401` and `403` are different.** `401` means the token is dead , get a new one. `403` means the token is fine and the answer is still no; retrying will not help.\n- **Collections are `GET /thing`, items are `GET /thing/{id}`.** Create is `POST` to the collection, update is `PATCH` to the item, delete is `DELETE` on the item. Where a resource deviates, the endpoint says so.\n- **Your organization is implied.** The token carries it. You never pass an organization id, and you can never read another organization's records.\n- **Ids are integers** and stable for the life of the record.\n- **Money is in cents**, as an integer. Never a float.\n\n## Time zones\n\nScheduling is anchored to the **airport's** time zone, not the caller's device. A booking made at 7am at the field is 7am at the field regardless of where the person making it happens to be.\n\nIn practice that means: send `start` and `end` with an **explicit UTC offset or `Z`**. A bare local datetime like `2026-08-04T15:00:00` is rejected , it would be read in the server's zone and silently book a different instant than the person picked. Send `timeZoneName` (an IANA zone) alongside so the booking knows which zone it was made in.\n\n## Reservation types\n\nWhich `type` a caller may create depends on the roles they hold. Roles are additive: an instructor who is also a technician may create both sets.\n\n| Role | May create |\n|---|---|\n| owner, admin, dispatcher | solo, dual, ground, guest, sim, rental, maintenance |\n| instructor | solo, dual, ground, guest, sim |\n| student | solo, dual, ground, sim |\n| renter | rental |\n| technician | maintenance |\n\n## Rate limits\n\nLimits are applied per signed-in account, not per IP, so a whole organization behind one office address does not share one budget.\n\n| | Limit |\n|---|---|\n| Per account | 300 requests/minute, 5,000/hour |\n| Unauthenticated | 100 requests per 5 minutes, per IP |\n| Unauthenticated endpoints | Tighter still, and limited per IP |\n\nEvery response carries `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`. A `429` carries `Retry-After` in seconds , honour it rather than retrying on a fixed interval.\n\n## Pagination\n\n**Every list is capped at 1,000 rows**, whether or not you ask for a limit. Pass `limit` and `offset` to page:\n\n```\nGET /reservations?startDate=…&endDate=…&limit=100&offset=200\n```\n\nEvery list response carries a `pagination` object beside `data`:\n\n```json\n{\n  \"data\": [ … ],\n  \"pagination\": { \"total\": 7823, \"limit\": 1000, \"offset\": 0, \"returned\": 1000, \"hasMore\": true }\n}\n```\n\n- `total` , how many there are in all, before the page was taken.\n- `returned` , how many are in `data` right now.\n- `hasMore` , whether another page exists. **Check it.** It is how you find out a collection is bigger than one page, and some of ours are: our largest organization has over 7,000 reservations.\n\nAsking for more than 1,000 gives you 1,000 rather than an error, so you never need to know the ceiling to write a working request. `offset` past the end returns an empty `data` and `hasMore: false`.\n\nCursor pagination is coming and will be additive , `limit`/`offset` will keep working.\n\n### Ordering\n\n`sort` names a field to order by, as a dot path into the row, and `order` is `asc` (the default) or `desc`:\n\n```\nGET /invoices?sort=total&order=desc&limit=50\n```\n\n**Sort here rather than after you receive the page.** Ordering is applied to the whole collection before the page is cut, so `sort=total&order=desc&limit=50` gives you the fifty largest invoices. Sorting the page yourself orders the fifty rows you happen to be holding, which is a different , and usually wrong , answer.\n\nNumbers order as numbers and ISO timestamps as instants, so `total` does not put `1000` before `90`. Empty values sort last in both directions: an invoice with no `paidAt` is not the most recent payment. Ties keep the endpoint's own order, so paging a sort by a coarse field like status will not skip or repeat rows.\n\nOmit `sort` and you get the order the endpoint has always used.\n\n## Versioning\n\n**The unprefixed paths are v1.** `https://api.aerscheduler.com/reservations` is the v1 reservations endpoint; there is no `/v1/` to add and adding one will 404. That is deliberate , a version segment you never change is decoration, and we would rather not pretend.\n\nThis document describes **v1.0.0**, which you can read at `info.version` in the spec.\n\n**What can change under you, without notice:**\n\n- New endpoints.\n- New fields on an existing response.\n- New optional request parameters.\n- New values in an enum you are already handling.\n\nSo parse leniently: ignore fields you do not recognise, and do not assume an enum is closed. An integration that breaks when we add a field is an integration that will break.\n\n**What will never change within v1:**\n\n- An endpoint disappearing, or moving.\n- A field disappearing from a response, or changing type.\n- A previously optional request parameter becoming required.\n- A success status code changing.\n\nAnything in that second list means **v2**, which would be served alongside v1 at `/v2/…` rather than replacing it. You would get notice by email before it existed, and v1 would keep working for a stated period afterwards , we are not going to break a customer's software to save ourselves a route table.\n\n**Deprecations** are announced in the changelog and carry a `Deprecation` header on the affected endpoint before anything is removed.",
    "contact": {
      "name": "AerScheduler support",
      "email": "support@aerscheduler.com",
      "url": "https://aerscheduler.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://aerscheduler.com/terms-and-conditions"
    },
    "termsOfService": "https://aerscheduler.com/terms-and-conditions"
  },
  "servers": [
    {
      "url": "https://api.aerscheduler.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Organizations",
      "description": "The flight school itself: membership, invitations, and settings."
    },
    {
      "name": "Members",
      "description": "People and the roles they hold."
    },
    {
      "name": "Reservations",
      "description": "Booking, rescheduling, and closing out flights."
    },
    {
      "name": "Cost splitting",
      "description": "How a booking's cost divides when more than one person is on it. Each person is billed separately, so a shared booking produces one invoice per payer. An organization that sets no rules bills one person for the whole booking."
    },
    {
      "name": "Availability",
      "description": "When people and aircraft are free."
    },
    {
      "name": "Resources",
      "description": "Aircraft, simulators, and rooms."
    },
    {
      "name": "Audit",
      "description": "Who changed what, and when."
    },
    {
      "name": "Maintenance",
      "description": "Squawks and maintenance reminders."
    },
    {
      "name": "Work orders",
      "description": "The shop's jobs on aircraft: one record per job, from the owner's call to the invoice. Owners, admins and technicians only."
    },
    {
      "name": "Aircraft owners",
      "description": "An aircraft owner's own aircraft and the jobs on them. Answered for whatever the caller owns or is billed for; anything else answers 404, exactly as an id that does not exist. The one part of the API an owner from outside the organization may call."
    },
    {
      "name": "Invoices",
      "description": "Billing for completed flights."
    },
    {
      "name": "Locations",
      "description": "Bases of operation."
    },
    {
      "name": "Groups",
      "description": "Sets of members or resources."
    },
    {
      "name": "Currencies",
      "description": "Recurring requirements members must stay current on."
    },
    {
      "name": "Training",
      "description": "Courses, syllabi, enrollments and training records. A course is trained under Part 61 or Part 141; the shape is identical and only the enforcement differs. A **published syllabus version is immutable** , revising it makes a new version, and an enrollment stays pinned to the one it started under. A student's standing against each requirement is a **ledger**: entries are only ever appended, and a correction is a reversing entry rather than an edit, so hours flown are always traceable to the lesson that earned them."
    },
    {
      "name": "Memberships",
      "description": "What a club or an FBO charges people for **belonging**, as opposed to for flying. A plan carries a one-time join fee, recurring dues, or both; a membership is one person's copy of a plan with the money **snapshotted at the moment it started**, so re-pricing a plan never re-prices anybody already on it. Every dues period is a ledger row, and a period is billed because the membership's own cursor says it is owed , never because of what day it happens to be. Dues and join fees are raised as ordinary invoices, so they appear under **Invoices** and in the revenue reports with everything else."
    },
    {
      "name": "Documents",
      "description": "Files held against members."
    },
    {
      "name": "Announcements",
      "description": "Notices to members."
    },
    {
      "name": "Notifications",
      "description": "Per-member in-app notifications."
    },
    {
      "name": "Standby",
      "description": "Standby interest, cancellation recovery, and time-limited slot offers."
    },
    {
      "name": "Booking requests",
      "description": "When org policy requires approval, members submit requests and desk staff approve or decline them before a reservation is created."
    },
    {
      "name": "Booking offerings",
      "description": "Presets over org calendar policy for guest pages and internal slot discovery, plus per-audience calendar visibility settings."
    },
    {
      "name": "Public booking",
      "description": "Unauthenticated guest request pages. A guest picks an offering slot, confirms by email, and the front desk approves or declines the request. Members keep booking as they do today."
    },
    {
      "name": "Reports",
      "description": "The reporting engine and saved views."
    },
    {
      "name": "Search",
      "description": "One search across the organization."
    },
    {
      "name": "Weather",
      "description": "Field observations."
    },
    {
      "name": "Service",
      "description": "Health and status."
    },
    {
      "name": "Realtime",
      "description": "WebSocket tickets for live console updates (schedule, notifications, billing)."
    },
    {
      "name": "Widgets",
      "description": "One compact read of what a member needs to see right now, sized for a home-screen widget or a wall display."
    }
  ],
  "paths": {
    "/users": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List users",
        "description": "Every person in your organization, as `User` records. Prefer `GET /orgUsers`, which carries roles.",
        "responses": {
          "200": {
            "description": "The users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listUsers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update your own profile",
        "description": "Changes your own name, phone, or other profile fields.",
        "responses": {
          "200": {
            "description": "Updated."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateOwnProfile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Members"
        ],
        "summary": "Delete your own account",
        "description": "Soft-deletes the signed-in account.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteOwnAccount"
      }
    },
    "/users/{id}": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a user",
        "description": "One person in your organization.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such user in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUser",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          }
        ]
      }
    },
    "/users/{userId}/roles": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a member's roles",
        "description": "The roles a person holds in your organization. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserRoles",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Set a member's roles",
        "description": "Replaces the whole role set , send every role you want them to keep, not just the change. Requires the **admin** role.\n\nTwo invariants are enforced: an organization must always have at least one owner and at least one administrator, and an owner must also be an administrator.",
        "responses": {
          "200": {
            "description": "Roles updated."
          },
          "400": {
            "description": "The change would leave the organization without an owner or an administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setUserRoles",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The complete role set.",
                "properties": {
                  "owner": {
                    "type": "boolean"
                  },
                  "admin": {
                    "type": "boolean"
                  },
                  "dispatcher": {
                    "type": "boolean"
                  },
                  "instructor": {
                    "type": "boolean"
                  },
                  "student": {
                    "type": "boolean"
                  },
                  "renter": {
                    "type": "boolean"
                  },
                  "technician": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/orgUser/archive": {
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Archive or restore a member",
        "description": "Retires somebody from the roster, or brings them back. Requires the **admin** role , a dispatcher cannot.\n\nAn archived member **is not notified, ever** , no email, no push, no in-app notice. They are left out of `GET /orgUsers` unless you pass `archived=true` (which is also how you list them again), and they cannot be added to a booking. Nothing is deleted: their flights, invoices and history are untouched, and restoring them is the same call with `false`.\n\nUse this rather than grounding to tidy a roster. **Grounding is a live restriction and emails the member**, so grounding a batch of people who stopped flying years ago sends each of them an unexpected notice.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`archived` was missing or not a boolean, or you tried to archive your own membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not an owner or admin of this organization, or that person is not a member of it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setMemberArchived",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id , not the membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Whether the member should be archived.",
                "properties": {
                  "archived": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "archived"
                ]
              }
            }
          }
        }
      }
    },
    "/users/sms": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get your SMS opt-in status",
        "description": "Whether your mobile is verified for transactional SMS. SMS is **US mobile numbers only**. When `available` is false, the product kill switch is off (`SMS_NOTIFICATIONS_ENABLED`) and clients should hide SMS UI. Category toggles still live on `GET/PATCH /orgUsers/preferences` (`smsEnabled` and `smsNotificationPreferences`).",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getSmsStatus"
      }
    },
    "/users/sms/verify/start": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Send an SMS verification code",
        "description": "Texts a one-time code to the US mobile on your profile. Required before `smsEnabled` can be turned on.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No US mobile on file, or the number cannot receive SMS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "startSmsVerification"
      }
    },
    "/users/sms/verify/confirm": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Confirm an SMS verification code",
        "description": "Marks the mobile as opted-in and verified when the code matches.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "verified": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Code missing, expired, or incorrect.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "confirmSmsVerification",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The code from the text.",
                "properties": {
                  "code": {
                    "type": "string",
                    "example": "123456"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/sms/opt-out": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Opt out of SMS",
        "description": "Turns off SMS for your account. Equivalent to replying STOP to an AerScheduler text.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "optedOut": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "optOutSms"
      }
    },
    "/users/{userId}/details": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a person's contact details",
        "description": "Phone numbers, date of birth, address and emergency contacts.\n\nReadable by the person themselves, an **admin** or **dispatcher** of your organization, or an **instructor** the person is assigned to as a student. Anyone else gets 403 , these are home phone numbers and next of kin, not roster data.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDetails"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not allowed to see this person's contact details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserContactDetails",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id. Use your own to read your own record."
          }
        ]
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update a person's contact details",
        "description": "Writable by the person themselves and by an **admin** or **dispatcher** , the front desk genuinely does take a corrected number over the counter. Instructors are read-only.\n\nEvery change is written to the audit trail, with phone numbers masked.\n\n**Phone numbers** are normalized to E.164 and validated; an unreal number is a 400, not a silent accept. Omit a field to leave it alone; send `null` or `\"\"` to clear it.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDetails"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A phone number or date was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not allowed to change this person's contact details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateUserContactDetails",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change. All optional.",
                "properties": {
                  "phone": {
                    "type": "string",
                    "description": "Primary/mobile number. Any format; stored as E.164."
                  },
                  "homePhone": {
                    "type": "string"
                  },
                  "workPhone": {
                    "type": "string"
                  },
                  "dateOfBirth": {
                    "type": "string",
                    "format": "date",
                    "description": "`YYYY-MM-DD`. Cannot be in the future."
                  },
                  "preferredName": {
                    "type": "string"
                  },
                  "sex": {
                    "type": "string"
                  },
                  "defaultCountry": {
                    "type": "string",
                    "description": "ISO 3166-1 alpha-2 used to read numbers sent without a country code. Defaults to `US`.",
                    "example": "US"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/emergencyContacts": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List a person's emergency contacts",
        "description": "Primary first. Same read permissions as `GET /users/{userId}/details`.",
        "responses": {
          "200": {
            "description": "The emergency contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmergencyContact"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listEmergencyContacts",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Add an emergency contact",
        "description": "`name` and a valid `phone` are required. Setting `isPrimary` clears it on the others.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmergencyContact"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A name and a valid phone number are required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createEmergencyContact",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new contact.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "relationship": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "description": "Required. Any format; stored as E.164."
                  },
                  "altPhone": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "isPrimary": {
                    "type": "boolean"
                  },
                  "sortOrder": {
                    "type": "integer"
                  },
                  "defaultCountry": {
                    "type": "string",
                    "description": "ISO 3166-1 alpha-2 for numbers with no country code."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/emergencyContacts/{contactId}": {
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update an emergency contact",
        "description": "Omit a field to leave it. `phone` may be changed but not cleared.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmergencyContact"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field was not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That emergency contact doesn't exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateEmergencyContact",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The emergency contact id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "relationship": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "altPhone": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "isPrimary": {
                    "type": "boolean"
                  },
                  "sortOrder": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Members"
        ],
        "summary": "Remove an emergency contact",
        "description": "Soft-deletes it.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That emergency contact doesn't exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteEmergencyContact",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The emergency contact id."
          }
        ]
      }
    },
    "/users/{userId}/approvedResources": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List a member's approved resources",
        "description": "Which aircraft this person has been checked out on.",
        "responses": {
          "200": {
            "description": "The approved resources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listApprovedResources",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/users/availability": {
      "get": {
        "tags": [
          "Availability"
        ],
        "summary": "Get your recurring availability",
        "description": "Your standing weekly availability pattern, keyed by day of week , an object, not a list, so it is not paged.",
        "responses": {
          "200": {
            "description": "Your weekly pattern.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "One entry per weekday; each is the list of windows you are normally free.",
                      "properties": {
                        "monday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "tuesday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "wednesday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "thursday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "friday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "saturday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        },
                        "sunday": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Availability"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOwnAvailability"
      },
      "patch": {
        "tags": [
          "Availability"
        ],
        "summary": "Set your recurring availability",
        "description": "Replaces your standing weekly availability. Keyed by day of week, the same shape the GET returns; send at least one day, and a day you omit is left alone. Each window is `{ start, end }` as **\"HH:mm\" in the organization's timezone**, not an ISO timestamp.",
        "responses": {
          "200": {
            "description": "Updated."
          },
          "400": {
            "description": "No day was supplied. Send at least one of `monday` through `sunday`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setOwnAvailability",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new pattern, by day. Omit a day to leave it unchanged.",
                "properties": {
                  "monday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "tuesday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "wednesday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "thursday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "friday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "saturday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  },
                  "sunday": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Availability"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List your organizations",
        "description": "Every organization the signed-in account belongs to.",
        "responses": {
          "200": {
            "description": "The organizations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Organization"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listOrganizations",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "patch": {
        "tags": [
          "Organizations"
        ],
        "summary": "Update your organization",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Organization"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateOrganization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "organizationType": {
                    "type": "string",
                    "enum": [
                      "flight_school",
                      "flying_club",
                      "rental",
                      "solo_instructor",
                      "aircraft_owner",
                      "maintenance_shop"
                    ],
                    "description": "What kind of operation this is. Same values as create. Changing it here does not rewrite signup attribution."
                  },
                  "about": {
                    "type": "string"
                  },
                  "showInDirectory": {
                    "type": "boolean"
                  },
                  "bookingPolicy": {
                    "type": "object",
                    "description": "Org-wide booking rules. Send only the keys you are changing; `null` clears a rule. Minimum notice, booking horizon, start interval, fixed duration, and buffers apply to every internal schedule booking (member self-book and front desk), not only future public embeds. The four balance gates apply only when the organization is in **ledger** billing mode.",
                    "properties": {
                      "minimumNoticeMinutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 15,
                        "maximum": 525600,
                        "description": "Minutes of notice a booking needs before it starts. Null = off. Range 15 to 525600 (1 year). Enforced on create and when the start time changes. Owners, admins, dispatchers, instructors, and technicians skip this on create."
                      },
                      "bookingHorizonDays": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 730,
                        "description": "How many calendar days ahead anyone may book, counted in the organization's time zone. Null = off (the clients still cap the picker at 365 days). Range 1 to 730. Enforced on create and when the start time changes. Staff skip this on create."
                      },
                      "startTimeIncrementMinutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "enum": [
                          15,
                          30,
                          60,
                          null
                        ],
                        "description": "Start times must land on this minute grid in the organization's time zone, measured from midnight. Null = off. Only 15, 30, or 60 are accepted. Requires an organization or airport time zone; the request is refused until one is set. Enforced when the start time changes."
                      },
                      "fixedReservationMinutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 15,
                        "maximum": 1440,
                        "description": "Every booking must be exactly this many minutes long. Null = off. Range 15 to 1440. Cannot exceed `maxReservationMinutes` when that rule is also set. Enforced when start or end changes. Maintenance is exempt."
                      },
                      "bufferBeforeMinutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 5,
                        "maximum": 720,
                        "description": "Idle minutes required before a booking. Null = off. Range 5 to 720. Independent of `bufferAfterMinutes`. Adjacent bookings need the before and after buffers stacked (added together) as idle time between them."
                      },
                      "bufferAfterMinutes": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 5,
                        "maximum": 720,
                        "description": "Idle minutes required after a booking. Null = off. Range 5 to 720. Independent of `bufferBeforeMinutes`. Adjacent bookings need the before and after buffers stacked (added together) as idle time between them."
                      },
                      "minimumBalanceCents": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Credit a student or renter needs to self-book. Null = off. 0 = cannot book while owing. Owners, admins and dispatchers booking on someone's behalf skip it."
                      },
                      "balanceMaximumCents": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Most a member may owe and still self-book. Null = off. 0 = any negative balance is refused."
                      },
                      "dispatchMinimumBalanceCents": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Same floor, checked at ramp-out instead of at book. Null = off, and the self-book floor is NOT re-applied. Only owners and admins override this one; dispatchers do not."
                      },
                      "dispatchBalanceMaximumCents": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Most a member may owe and still ramp out. Null = off."
                      }
                    }
                  },
                  "publicBookingEnabled": {
                    "type": "boolean",
                    "description": "When true, guests can submit booking requests for active offerings at /book/{publicBookingSlug}/{offering.slug}. Staff still approve every request."
                  },
                  "publicBookingSlug": {
                    "type": "string",
                    "description": "Lowercase letters, numbers, and hyphens. Unique across organizations. Generated from the organization name if you enable public booking without one."
                  },
                  "publicBookingEmbedHosts": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "string",
                      "format": "uri"
                    },
                    "description": "HTTPS origins allowed to iframe /book pages, stored like https://www.yoursite.com. Empty keeps the share link only. No wildcards, no IP addresses except localhost/127.0.0.1. Public hosts must be https. Demo organizations are refused."
                  },
                  "publicBookingAccentHex": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Guest /book accent, #RRGGBB. Null clears it."
                  },
                  "publicBookingAppearance": {
                    "type": "string",
                    "enum": [
                      "light",
                      "dark",
                      "system"
                    ]
                  },
                  "publicBookingDensity": {
                    "type": "string",
                    "enum": [
                      "compact",
                      "comfortable"
                    ]
                  },
                  "publicBookingCornerStyle": {
                    "type": "string",
                    "enum": [
                      "rounded",
                      "sharp"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Organizations"
        ],
        "summary": "Schedule your organization for deletion",
        "description": "Puts the organization on a **30-day** countdown. The organization stays usable until then. Every admin and owner is emailed and notified. Requires the **owner** role. Refused while any invoice is unpaid, or if a countdown is already running.",
        "responses": {
          "200": {
            "description": "The organization with its deletion date.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        },
                        "scheduledDeletionAt": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unpaid invoices, already scheduled, or a demo organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteOrganization"
      }
    },
    "/organizations/adTracking": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "Read the Airworthiness Directive tracking mode and readiness",
        "description": "Whether this organization wants AerScheduler involved in Airworthiness Directives, and how well it could be. `mode` is one of `off` (we do nothing about ADs), `manual` (the organization enters ADs itself), `catalogue` (we watch published ADs and propose the ones that mention this fleet) or `external` (applicability is tracked in another system, named by `externalSystem`).\n\n`aircraft` reports, per aeroplane, how precisely a published AD could be matched: `serial` when we can narrow the AD's serial-number range, `model` when we can only tell that an AD mentions this make and model, and `none` when we do not know enough to propose anything. **Admin only.**",
        "responses": {
          "200": {
            "description": "The mode and what could be matched for each aeroplane.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "mode": {
                          "type": "string",
                          "enum": [
                            "off",
                            "manual",
                            "catalogue",
                            "external"
                          ]
                        },
                        "externalSystem": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Which system tracks applicability, when mode is `external`."
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "serial": {
                              "type": "integer"
                            },
                            "model": {
                              "type": "integer"
                            },
                            "none": {
                              "type": "integer"
                            }
                          }
                        },
                        "aircraft": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "resourceId": {
                                "type": "integer"
                              },
                              "tailNumber": {
                                "type": "string"
                              },
                              "make": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "model": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "serialNumber": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "quality": {
                                "type": "string",
                                "enum": [
                                  "serial",
                                  "model",
                                  "none"
                                ]
                              },
                              "missing": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getAdTracking"
      },
      "patch": {
        "tags": [
          "Organizations"
        ],
        "summary": "Set the Airworthiness Directive tracking mode",
        "description": "Changes what AerScheduler does about ADs. Setting `external` requires `externalSystem`, which is printed on the AD status report so a reader knows applicability lives elsewhere. Returns the same shape as the GET, including refreshed readiness. **Admin only.**",
        "responses": {
          "200": {
            "description": "The updated mode and readiness.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unknown mode, or `external` with no system named.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setAdTracking",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mode"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "manual",
                      "catalogue",
                      "external"
                    ]
                  },
                  "externalSystem": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "description": "Required when mode is `external`. Cleared otherwise."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations/cancelDeletion": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Cancel a scheduled organization deletion",
        "description": "Clears the 30-day countdown. Requires the **admin** role (owners hold admin).",
        "responses": {
          "200": {
            "description": "The organization with the countdown cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        },
                        "scheduledDeletionAt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No deletion is currently scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "cancelOrganizationDeletion"
      }
    },
    "/organizations/ledger/accounts": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List member account balances",
        "description": "Organization-wide ledger roster for the Operations Billing desk. **Admin only.** Current members only (not archived, not API keys). `summary` is org-wide and ignores `q` / `status`. Default sort is most owing first (`balanceCents` ascending).",
        "responses": {
          "200": {
            "description": "Paged accounts plus org-wide totals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LedgerAccount"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "summary": {
                      "$ref": "#/components/schemas/LedgerAccountSummary"
                    }
                  },
                  "required": [
                    "data",
                    "pagination",
                    "summary"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not an administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listOrganizationLedgerAccounts",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "owing,credit"
            },
            "description": "Comma-separated balance buckets: `owing` (negative), `credit` (positive), `zero`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/organizations/ledger": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "Get ledger billing mode",
        "description": "Whether this organization is in ledger billing mode. When on, members live on an account balance; money-in and refunds are separate from per-flight invoices. Default off. Any member may read.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationLedgerSettings"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrganizationLedgerSettings"
      },
      "patch": {
        "tags": [
          "Organizations"
        ],
        "summary": "Set ledger billing mode",
        "description": "Turns ledger mode on or off, sets the optional card surcharge on member top-ups, and/or sets late fees on negative balances. **Owner only.** Desk cash/check credit is never surcharged. Late fees post once per member per calendar month after the grace period.",
        "responses": {
          "200": {
            "description": "Updated settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationLedgerSettings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only the organization owner can change this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateOrganizationLedgerSettings",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Ledger settings patch. Send any subset.",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "topUpCardFeePercent": {
                    "type": "integer",
                    "nullable": true
                  },
                  "topUpCardFeeFlatCents": {
                    "type": "integer",
                    "nullable": true
                  },
                  "lateFeePercent": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Whole percent of the amount owing. 0 or null = off."
                  },
                  "lateFeeFlatCents": {
                    "type": "integer",
                    "nullable": true
                  },
                  "lateFeeGraceDays": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Days owing before a late fee posts. Null = 30. 0 = first eligible sweep."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations/{orgId}": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "Get an organization",
        "description": "Public detail for one organization.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Organization"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrganization",
        "parameters": [
          {
            "name": "orgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The organization id."
          }
        ]
      }
    },
    "/organizations/invite": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Invite someone",
        "description": "Emails an invitation with the roles they will get on acceptance. At least one role must be true. Joining uses the invitation up: somebody who leaves and comes back joins like anybody else. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "No role chosen, already a member, or a group that is not this organization's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "inviteMember",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Who to invite and as what.",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "admin": {
                    "type": "boolean"
                  },
                  "instructor": {
                    "type": "boolean"
                  },
                  "student": {
                    "type": "boolean"
                  },
                  "renter": {
                    "type": "boolean"
                  },
                  "technician": {
                    "type": "boolean"
                  },
                  "dispatcher": {
                    "type": "boolean"
                  },
                  "orgUserGroupIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        }
      }
    },
    "/organizations/removeUser/{userId}": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Remove a member",
        "description": "Removes someone from your organization. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "They cannot be removed , the last owner or administrator, for instance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "removeMember",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user to remove."
          }
        ]
      }
    },
    "/organizations/leave/{orgId}": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Leave an organization",
        "description": "Removes yourself. The last owner cannot leave. An aircraft owner from outside the organization hands its record of them back instead (their aircraft, jobs and bills stay with the organization), and their saved payment methods and autopay are removed from it.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "You cannot leave , you are the last owner or administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "leaveOrganization",
        "parameters": [
          {
            "name": "orgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The organization to leave."
          }
        ]
      }
    },
    "/organizations/guests": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List guests",
        "description": "Non-members who have appeared on bookings.",
        "responses": {
          "200": {
            "description": "The guests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listGuests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/organizations/ratings": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List ratings",
        "description": "The certificates and ratings your organization teaches.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listRatings",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Create a rating",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createRating",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rating.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "anyInstructorCanTeach": {
                    "type": "boolean"
                  },
                  "defaultInstructorRate": {
                    "type": "integer",
                    "description": "Hourly rate in cents."
                  }
                },
                "required": [
                  "name",
                  "anyInstructorCanTeach",
                  "defaultInstructorRate"
                ]
              }
            }
          }
        }
      }
    },
    "/organizations/ratings/{ratingId}": {
      "patch": {
        "tags": [
          "Organizations"
        ],
        "summary": "Update a rating",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateRating",
        "parameters": [
          {
            "name": "ratingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The rating id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Organizations"
        ],
        "summary": "Delete a rating",
        "description": "Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteRating",
        "parameters": [
          {
            "name": "ratingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The rating id."
          }
        ]
      }
    },
    "/invitations": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List pending invitations",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listInvitations",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/joinRequests": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "List join requests",
        "description": "People asking to join a private organization. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listJoinRequests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/joinRequests/{id}/accept": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Accept a join request",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "acceptJoinRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The join request id."
          }
        ]
      }
    },
    "/joinRequests/{id}/decline": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Decline a join request",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "declineJoinRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The join request id."
          }
        ]
      }
    },
    "/joinRequests/{id}": {
      "delete": {
        "tags": [
          "Organizations"
        ],
        "summary": "Delete a join request",
        "description": "Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteJoinRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The join request id."
          }
        ]
      }
    },
    "/orgUsers": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List members",
        "description": "Every member of your organization with the roles they hold. **This is the endpoint for “who is in this organization”**, including “who are the instructors” , pass the role as a filter rather than looking for a per-role endpoint.\n\nRole filters combine as OR: `?instructor=true&student=true` returns anyone holding either. Roles are additive, so one person can come back under several filters.",
        "responses": {
          "200": {
            "description": "The members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrganizationUser"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listOrgUsers",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **owner** role."
          },
          {
            "name": "admin",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **admin** role."
          },
          {
            "name": "dispatcher",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **dispatcher** role."
          },
          {
            "name": "instructor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **instructor** role."
          },
          {
            "name": "student",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **student** role."
          },
          {
            "name": "renter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **renter** role."
          },
          {
            "name": "technician",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **technician** role."
          },
          {
            "name": "noRoles",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to return only members with no role at all , the people who have joined but not been set up yet. Overrides every other role filter."
          },
          {
            "name": "groupId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated member-group ids to filter by."
          },
          {
            "name": "grounded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by grounded state."
          },
          {
            "name": "archived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Archived members , people the organization has retired from the roster , are **left out by default**. Set `true` to list them instead of the current members. There is no value that returns both."
          },
          {
            "name": "approvedForResourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "An aircraft id. Every member returned carries `approvedForResource`, saying whether they are checked out on that aircraft , so you can read approvals for a whole roster in one request instead of one per person."
          },
          {
            "name": "approved",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "With `approvedForResourceId`, set `true` to return **only** the members approved on that aircraft rather than annotating them all. Ignored on its own."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a member",
        "description": "One membership record, with roles.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationUser"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such member in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUser",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id. Note this is NOT the user id."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ledger": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a member's ledger",
        "description": "Account balance and append-only ledger entries for one member. **Self or admin.** When ledger mode is on, money-in and refunds are separate write routes under this path. `ledgerEnabled` tells you whether the org is in ledger billing mode; the balance is still returned when mode is off (usually 0).",
        "responses": {
          "200": {
            "description": "Balance plus a page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MemberLedger"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not in your organization, or not allowed to view another member's ledger.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUserLedger",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "topup,flight_charge"
            },
            "description": "Comma-separated ledger entry types to include (`topup`, `cash`, `check`, `other`, `adjustment`, `flight_charge`, `item_charge`, `fee`, `refund`, `reversal`)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows of entries to return, 1–1000. Defaults to 1000."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Entries to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "createdAt",
                "amountCents",
                "type",
                "id"
              ]
            },
            "description": "Order entries by `createdAt`, `amountCents`, `type`, or `id`. Defaults to `createdAt`."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "`asc` or `desc`. Defaults to `desc`."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ledger/statement": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a member ledger statement",
        "description": "Inclusive period statement: opening balance (sum of entries before `start`), period entries oldest-first with running balance, and closing. **Self or admin.** Period must be under two years and under 5000 entries.",
        "responses": {
          "200": {
            "description": "Statement for the requested period.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerStatement"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid range, or too many rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed to view this member's ledger.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUserLedgerStatement",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          },
          {
            "name": "start",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive period start. ISO 8601 instant, or `YYYY-MM-DD` for the start of that calendar day in the organization's time zone (falls back to the viewer zone, then UTC)."
          },
          {
            "name": "end",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive period end. ISO 8601 instant, or `YYYY-MM-DD` for the end of that calendar day in the organization's time zone (falls back to the viewer zone, then UTC)."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ledger/statement/email": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Email a member ledger statement",
        "description": "Sends the same period statement to the member's email. **Self or admin.** Not a Stripe invoice and not synced to QuickBooks.",
        "responses": {
          "200": {
            "description": "Email queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "boolean"
                        },
                        "to": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "sent",
                        "to"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid range, or no email on file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed to email this member's statement.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The statement email could not be sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "emailOrgUserLedgerStatement",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Statement period.",
                "properties": {
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Inclusive period start. ISO 8601 instant, or `YYYY-MM-DD` for the start of that calendar day in the organization's time zone (falls back to the viewer zone, then UTC)."
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Inclusive period end. ISO 8601 instant, or `YYYY-MM-DD` for the end of that calendar day in the organization's time zone (falls back to the viewer zone, then UTC)."
                  },
                  "periodLabel": {
                    "type": "string",
                    "description": "Optional `YYYY-MM-DD to YYYY-MM-DD` printed in the email. Send the calendar days the member picked so the subject matches the organization's days, not a UTC slice of instants."
                  }
                },
                "required": [
                  "start",
                  "end"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/auto-refill": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get ledger auto-refill",
        "description": "The member's auto-refill (Auto Pay) settings. Missing row returns defaults with `enabled: false`. **Self or admin.**",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerAutoRefill"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUserLedgerAutoRefill",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update ledger auto-refill",
        "description": "Create or update auto-refill. Charges the default saved card on the cadence when ledger mode is on. **Self or admin.** Three modes: `under_threshold`, `pay_balance`, `fixed_amount`. Cadence `daily` or `monthly` (day 1–28). The hourly cron runs in the organization's time zone after `runHourLocal` (default 4).",
        "responses": {
          "200": {
            "description": "Saved settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerAutoRefill"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or ledger mode is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateOrgUserLedgerAutoRefill",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Auto-refill patch. Send any subset.",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "under_threshold",
                      "pay_balance",
                      "fixed_amount"
                    ]
                  },
                  "thresholdCents": {
                    "type": "integer",
                    "nullable": true
                  },
                  "chargeCents": {
                    "type": "integer",
                    "nullable": true
                  },
                  "cadence": {
                    "type": "string",
                    "enum": [
                      "daily",
                      "monthly"
                    ]
                  },
                  "monthlyDay": {
                    "type": "integer",
                    "nullable": true
                  },
                  "runHourLocal": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 23
                  }
                }
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/topups": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Start a ledger card top-up",
        "description": "Creates a PaymentIntent on the organization's connected Stripe account to credit this member's ledger. **Self or admin.** Requires ledger mode. Minimum $1. Pass `paymentMethodId` to confirm immediately with a saved card (native clients); otherwise return the client secret for Stripe.js.",
        "responses": {
          "201": {
            "description": "PaymentIntent created (and confirmed when paymentMethodId was sent).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerTopUpIntent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ledger off, amount too small, or Stripe rejected the charge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createOrgUserLedgerTopUp",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Top-up amount.",
                "properties": {
                  "amountCents": {
                    "type": "integer",
                    "description": "Amount in cents. Minimum 100.",
                    "minimum": 100
                  },
                  "paymentMethodId": {
                    "type": "string",
                    "description": "Optional saved Stripe payment method to confirm off-session."
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "description": "Optional Stripe idempotency key."
                  }
                },
                "required": [
                  "amountCents"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/topups/confirm": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Confirm a ledger card top-up",
        "description": "After Stripe.js confirms the PaymentIntent, post the credit immediately so Home and Billing do not wait on `payment_intent.succeeded`. **Self or admin.** Idempotent with the webhook via the PaymentIntent id.",
        "responses": {
          "200": {
            "description": "Credit posted (or already posted) and current balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerTopUpConfirm"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Ledger off, PI not succeeded, or not this member's top-up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "confirmOrgUserLedgerTopUp",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Confirmed PaymentIntent.",
                "properties": {
                  "paymentIntentId": {
                    "type": "string",
                    "description": "Stripe PaymentIntent id from the top-up create response."
                  }
                },
                "required": [
                  "paymentIntentId"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/entries": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Post an admin ledger credit or adjustment",
        "description": "Desk money-in without Stripe: `cash`, `check`, or `other` (positive cents), or `adjustment` (signed). **Admin only.** Memo required. Ledger mode must be on.",
        "responses": {
          "201": {
            "description": "Posted entry and new balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "balanceCents": {
                          "type": "integer"
                        },
                        "entry": {
                          "$ref": "#/components/schemas/LedgerEntry"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not an admin, ledger off, or validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createOrgUserLedgerEntry",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Credit or adjustment.",
                "properties": {
                  "amountCents": {
                    "type": "integer",
                    "description": "Positive for credits; signed for adjustments."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "check",
                      "other",
                      "adjustment"
                    ]
                  },
                  "memo": {
                    "type": "string",
                    "description": "Required note (check #, reason, …)."
                  }
                },
                "required": [
                  "amountCents",
                  "type",
                  "memo"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/entries/{entryId}/receipt": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a ledger charge receipt",
        "description": "Receipt for a `flight_charge`, `item_charge`, or `fee` entry: line items, member, org, and optional reservation summary. **Self or admin.** Replaces Stripe PDF in ledger mode.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerReceipt"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a charge entry, or not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entry not found for this member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUserLedgerReceipt",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Ledger entry id."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ledger/entries/{entryId}/reassign": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Reassign a flight charge",
        "description": "Moves a `flight_charge` to another member by reversing the original and posting a new charge (never UPDATE). **Admin only.** Updates the reservation payer stake link.",
        "responses": {
          "201": {
            "description": "Reversal on the original member and new charge on the target.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "reversal": {
                          "$ref": "#/components/schemas/LedgerEntry"
                        },
                        "entry": {
                          "$ref": "#/components/schemas/LedgerEntry"
                        },
                        "fromBalanceCents": {
                          "type": "integer"
                        },
                        "toBalanceCents": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not an admin, not a flight charge, already reversed, or validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "reassignOrgUserLedgerFlightCharge",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Current member who holds the charge."
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Flight charge ledger entry id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Target member.",
                "properties": {
                  "toOrgUserId": {
                    "type": "integer",
                    "description": "Membership id that should own the charge."
                  },
                  "memo": {
                    "type": "string",
                    "description": "Optional note on the reversal / new charge."
                  }
                },
                "required": [
                  "toOrgUserId"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/entries/{entryId}/reverse": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Reverse a flight charge",
        "description": "Posts an opposing entry that cancels a `flight_charge`, so the flight behind it can be corrected. **Admin only.**\n\n**This is the ledger's counterpart to voiding an invoice.** Correcting a recorded reading is refused while live money stands against the booking, and at a ledger organization the charge posts automatically the moment the last pilot enters their PIN, so the booking locks on the same tap that finishes it. Reverse the charge, correct the reading, let the crew re-confirm, and the flight bills again from the corrected hours.\n\n**Money is countered, never rewritten.** The member's statement keeps the original charge and shows the reversal beside it. A charge that has already been reversed is refused, and a reversal cannot itself be reversed.\n\nOnly a `flight_charge` can be reversed here. Correct a top-up, a membership due or a manual adjustment by posting an adjustment of its own.",
        "responses": {
          "201": {
            "description": "The reversing entry, and the balance it leaves.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "entry": {
                          "$ref": "#/components/schemas/LedgerEntry"
                        },
                        "balanceCents": {
                          "type": "integer",
                          "description": "The member's balance after the reversal."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a flight charge, already reversed, or an invalid entry id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Admin only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Entry not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "reverseOrgUserLedgerFlightCharge",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Member who holds the charge."
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Flight charge ledger entry id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optionally, why.",
                "properties": {
                  "memo": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Kept on the reversing entry, so the statement says why."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/ledger/refundable": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List refundable top-ups",
        "description": "Current balance plus card top-ups with remaining refundable cents. **Self or admin.** Used by the desk Refund UI.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LedgerRefundable"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not in your organization, or not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrgUserLedgerRefundable",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ledger/refunds": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Refund from a member ledger",
        "description": "First-class refund. `method: stripe` refunds a top-up PaymentIntent then posts a `refund` entry; `check_cash` posts the ledger entry only (organization pays out of band). **Admin only.** Capped by balance and, for Stripe, by that top-up's remaining refundable cents.",
        "responses": {
          "201": {
            "description": "Refund posted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "balanceCents": {
                          "type": "integer"
                        },
                        "entry": {
                          "$ref": "#/components/schemas/LedgerEntry"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Cap exceeded, Stripe rejected, ledger off, or validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createOrgUserLedgerRefund",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Refund request.",
                "properties": {
                  "amountCents": {
                    "type": "integer",
                    "description": "Positive cents to remove from the account.",
                    "minimum": 1
                  },
                  "method": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "check_cash"
                    ]
                  },
                  "memo": {
                    "type": "string",
                    "description": "Required. Check # / reason."
                  },
                  "topupEntryId": {
                    "type": "integer",
                    "description": "Required for stripe; optional provenance for check_cash."
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "description": "Optional Stripe idempotency key for stripe refunds."
                  }
                },
                "required": [
                  "amountCents",
                  "method",
                  "memo"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/users": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get the person behind a membership",
        "description": "Resolves a membership id to the underlying person. Useful when you have an `orgUserId` from a booking and want the account behind it , the two ids are different and are not interchangeable.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "That membership is not in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserForOrgUser",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      }
    },
    "/orgUsers/preferences": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get your notification preferences",
        "description": "What you have opted into for this organization: email, push, and SMS masters plus per-category switches. SMS also requires a verified US mobile via `/users/sms`. Read-only facts ride along for the maintenance shop's settings: `runsShop` (the organization looks after customer aircraft), `hasTechnicians` (it has signed-in technicians; with none, its admins hear new requests by default), and `effectiveCustomerInspectionScope` (`worked_on`, `all` or `off`: the customer-inspection setting the server uses for you, your own or your role's default).",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getPreferences"
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update your notification preferences",
        "description": "Changes only the keys you send. Setting `smsEnabled: true` requires a verified US mobile.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updatePreferences",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Preferences to change.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/reservations": {
      "get": {
        "tags": [
          "Reservations"
        ],
        "summary": "List reservations",
        "description": "Bookings overlapping a date window. **`startDate` and `endDate` are required** , unlike every other list in this API, this one will not return an unbounded set.",
        "responses": {
          "200": {
            "description": "The bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The date range is missing or unparseable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReservations",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "12,15"
            },
            "description": "Comma-separated resource ids to filter by."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated location ids to filter by."
          },
          {
            "name": "includeCanceled",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include cancelled bookings. Defaults to false."
          },
          {
            "name": "ongoing",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `false` to exclude bookings already in progress. Defaults to true."
          },
          {
            "name": "orderBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction on start time. Defaults to `desc`."
          },
          {
            "name": "uninvoiced",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` for only the bookings with no live Stripe invoice and no live ledger flight_charge."
          },
          {
            "name": "endedBefore",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only bookings that finished before this instant, ISO 8601. Note the date range is an *overlap* query , narrowing `endDate` will not exclude a flight that is still out, and this will."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Create a reservation",
        "description": "Books a resource, a person, or both.\n\n`start` and `end` **must carry an explicit UTC offset or `Z`**. A bare local datetime is rejected: it would be read in the server's zone and silently book a different instant than the customer picked.\n\nWhich `type` values you may create depends on your roles , see the Reservation types section. The server also refuses double-bookings and bookings on grounded aircraft.\n\nPass a `recurrence` object to create a repeating series. Occurrences are real reservations, so each one can be ramped, reviewed, and invoiced independently.",
        "responses": {
          "201": {
            "description": "The booking that was created, or the series.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Rejected: a clash, a grounded aircraft, an ambiguous timestamp, or a type your roles may not create.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createReservation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The booking.",
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 50,
                    "description": "Short label shown on the calendar.",
                    "example": "Discovery flight"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "solo",
                      "shared",
                      "dual",
                      "ground",
                      "guest",
                      "sim",
                      "rental",
                      "maintenance"
                    ],
                    "description": "What kind of booking. Restricted by the caller's roles , see the table above.\n\n`shared` is several pilots with no instructor , two pilots splitting a cross-country, or a safety-pilot arrangement under 91.109. `solo` means ONE occupant (14 CFR 61.87) and takes exactly one person; an instructor aboard makes it a `dual`."
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-08-04T15:00:00-06:00",
                    "description": "Start instant. Must carry an explicit offset or `Z`."
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time",
                    "example": "2026-08-04T17:00:00-06:00",
                    "description": "End instant. Must carry an explicit offset or `Z`."
                  },
                  "timeZoneName": {
                    "type": "string",
                    "example": "America/Denver",
                    "description": "IANA zone the booking is being made in , the field's zone, not the caller's device."
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Free-text notes shown on the booking."
                  },
                  "resource": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    },
                    "description": "The aircraft, simulator, or room to book, as `{ id }`."
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    },
                    "description": "Where it happens, as `{ id }`. Defaults to the resource's location."
                  },
                  "personnel": {
                    "type": "object",
                    "description": "Who is on the booking. One person cannot fill two seats on the same booking.\n\n**Several people may share a side.** How many depends on the booking type: a `ground` class takes up to 12 students, `sim` up to 6, `dual` and `solo` up to 4, and `rental` up to 4 renters. `maintenance` takes nobody and `guest` takes one guest. Exceeding a limit is a 400 naming the side and the number allowed.\n\n**Order matters.** The first person on a side is the leading payer: they are billed when the organization's rule for a charge is \"one person pays\", and they take the remainder cent of an uneven division.\n\nEveryone listed must be free for the slot and must meet the currency and grounding rules , the booking is refused naming whoever does not, not silently trimmed.",
                    "properties": {
                      "instructors": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer"
                            }
                          }
                        }
                      },
                      "students": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer"
                            }
                          }
                        }
                      },
                      "renters": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer"
                            }
                          }
                        }
                      },
                      "guests": {
                        "type": "array",
                        "items": {
                          "type": "object"
                        }
                      }
                    }
                  },
                  "recurrence": {
                    "type": "object",
                    "description": "Optional. Creates a repeating series."
                  },
                  "collectionStyle": {
                    "type": "string",
                    "enum": [
                      "close_out",
                      "prepaid_fixed"
                    ],
                    "description": "When to collect. close_out (default) bills after the flight. prepaid_fixed invoices prepaidAmountCents when the reservation is created. Same style for desk calendar bookings and public offering approvals. Invoice-mode members and all guests get a Stripe invoice. Ledger-mode members get a ledger debit. Refused on recurring series and maintenance. Cancelling voids an unpaid invoice, refunds a paid one, or reverses the ledger debit."
                  },
                  "prepaidAmountCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Package price in cents. Required when collectionStyle is prepaid_fixed, at least 50."
                  }
                },
                "required": [
                  "title",
                  "type",
                  "start",
                  "end",
                  "timeZoneName"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/{id}": {
      "get": {
        "tags": [
          "Reservations"
        ],
        "summary": "Get a reservation",
        "description": "One booking, with personnel and close-out figures. Invoice totals, Stripe pay links, and paymentOverrides follow the same rule as GET /invoices/{id}: a member does not receive another person's share or rate.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such booking in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getReservation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Reservations"
        ],
        "summary": "Update a reservation",
        "description": "Reschedules or edits a booking. The same clash and role rules as create apply.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Rejected , usually a clash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateReservation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "notes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Reservations"
        ],
        "summary": "Cancel a reservation",
        "description": "Cancels rather than destroys: the booking stays readable with `cancelledAt` set, which is what keeps cancellation reporting honest. Both fields are required , `cancellationCategory` is what the report counts, `cancellationReason` is the detail that makes a count actionable. Get the current list of categories from `GET /reports/cancellations/categories`; sending a value that isn't on it is rejected.\n\n`409 RESERVATION_RAMPED_OUT` once the aircraft has left: a flight that has flown cannot be cancelled, because ramp-in has already advanced the aircraft's own Hobbs and tach and cancelling cannot put them back. Void any invoice, reopen the close-out, and correct the readings instead.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "It is already cancelled, or too late to cancel under your organization's policy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`RESERVATION_RAMPED_OUT`: the aircraft has already left the ramp. Reopen the close-out and correct it instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "cancelReservation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Why it was cancelled.",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "category": {
                    "type": "string"
                  }
                },
                "required": [
                  "reason",
                  "category"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/resource/{resourceId}": {
      "get": {
        "tags": [
          "Reservations"
        ],
        "summary": "List a resource's reservations",
        "description": "Bookings for one aircraft, simulator, or room in a window.",
        "responses": {
          "200": {
            "description": "The bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReservationsForResource",
        "parameters": [
          {
            "name": "resourceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/reservations/user/{userId}": {
      "get": {
        "tags": [
          "Reservations"
        ],
        "summary": "List a person's reservations",
        "description": "Bookings a person is on, in a window.",
        "responses": {
          "200": {
            "description": "The bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReservationsForUser",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/reservations/{id}/rampOut": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Ramp out",
        "description": "Records departure meter readings and marks the aircraft off the ramp. The first step of closing a flight out.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The aircraft is already ramped out, or you are not on this booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "rampOut",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Departure readings.",
                "properties": {
                  "hobbsTimeOut": {
                    "type": "number"
                  },
                  "tachTimeOut": {
                    "type": "number"
                  }
                },
                "required": [
                  "hobbsTimeOut",
                  "tachTimeOut"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/rampIn": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Ramp in",
        "description": "Records return meter readings and puts the aircraft back on the ramp.\n\n**Meter anomaly guard.** A reading that looks like a typo, far more digits than the out reading, or hours flown wildly beyond what the booking could have covered, is rejected with `409` and `code: \"METER_ANOMALY\"` rather than silently accepted. The response's `details.anomalies` describes what looked wrong. Resubmit the same body with `confirmMeterAnomaly: true` once the desk has confirmed the reading is real.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not ramped out, or you are not on this booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The reading looks like a typo. See `details.anomalies`; resubmit with `confirmMeterAnomaly: true` to override.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "rampIn",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Return readings.",
                "properties": {
                  "hobbsTimeIn": {
                    "type": "number"
                  },
                  "tachTimeIn": {
                    "type": "number"
                  },
                  "confirmMeterAnomaly": {
                    "type": "boolean",
                    "description": "Set true to accept a reading the anomaly guard flagged and resubmit despite it."
                  }
                },
                "required": [
                  "hobbsTimeIn",
                  "tachTimeIn"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/confirmReview": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Sign off the close-out",
        "description": "Confirms the recorded times. A dual flight needs both seats to confirm before it will invoice, which is why one person cannot occupy two seats on a booking.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Nothing to confirm yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "confirmReview",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/updateReviewTimes": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Correct recorded times",
        "description": "Rewrites a reading already recorded on a flight: Hobbs, tach, or the instruction time on a ground lesson. Only a figure the booking already holds can be corrected, so this opens once the aircraft is back and not before.\n\n**Every sign-off on the booking is cleared** and the crew confirm the new figures. Clearing a sign-off that is not your own needs the same authority as reopening (an **admin**, a **dispatcher**, or the **instructor on this booking**); re-entering your own PIN afterwards does not. Refused with `409 RESERVATION_BILLED` once the flight has been billed: void the invoice (or reverse the ledger charge) first.\n\n**A body that matches what is already stored writes nothing.** Both first-party forms are prefilled, so an unedited Save is a full echo of the stored figures. That is answered `200` with `noChanges: true`, and no sign-off is touched.\n\n**The aircraft's own meters follow only when this flight is still what they read.** Correcting an older booking leaves them alone rather than rolling the aircraft's logbook backwards; the response's `meter.followed` says which happened, and `meter.aircraftHobbs` / `meter.aircraftTach` report what the aircraft still reads so it can be corrected separately.\n\n**Two confirmable refusals.** `409 METER_ANOMALY` when the reading looks like a typo; `409 MAINTENANCE_TRIGGER` when saving it would pass an hour-based inspection and ground the aircraft. Resubmit with `confirmMeterAnomaly: true` / `confirmMaintenanceTrigger: true` to accept.",
        "responses": {
          "200": {
            "description": "The corrected booking, plus a report of what this call did to the aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    },
                    "meter": {
                      "type": "object",
                      "description": "What happened to the AIRCRAFT's own meters, as opposed to the booking's.",
                      "properties": {
                        "changed": {
                          "type": "boolean",
                          "description": "Did this correction touch a meter at all? False for a briefing-only fix."
                        },
                        "followed": {
                          "type": "boolean",
                          "description": "Did the aircraft's own Hobbs and tach move with the correction?"
                        },
                        "aircraftHobbs": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "What the aircraft still reads, in tenths, when `followed` is false."
                        },
                        "aircraftTach": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "unlatched": {
                          "type": "array",
                          "description": "Hour-based inspections whose reminder was un-latched because the reading came back below their due point.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "reminderId": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "notUnlatched": {
                          "type": "array",
                          "description": "Inspections this reading no longer earns but that were deliberately LEFT flagged, because their template also runs on a calendar clock (\"every 100 hours or 12 months\", which is most ADs and every annual). One latch serves all three clocks and nothing records which one set it, so an hour reading has no standing to clear it. Somebody has to clear these by hand.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "reminderId": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "writeFailed": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Set when the booking WAS corrected but pushing the reading onto the aircraft failed. Distinct from `followed: false`, which is the ordinary \"this is not the flight that set the aircraft's meter\" case."
                        }
                      }
                    },
                    "noChanges": {
                      "type": "boolean",
                      "description": "The submitted figures already matched what was stored, so nothing was written and no sign-off was cleared."
                    },
                    "splitLegsStale": {
                      "type": "boolean",
                      "description": "The per-person legs of a `measured` cost split still describe the OLD hours. Nothing here rewrites them, because dividing a flight between people is a human decision, but the invoice will refuse to generate until they are re-entered."
                    },
                    "guestCloseOutCleared": {
                      "type": "boolean",
                      "description": "A guest booking's close-out was cleared, so it needs closing out again before it can be billed."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Nothing to correct in the body, the booking holds no such figure, or it was cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not on this booking, or it carries somebody else's sign-off and you are not an admin, a dispatcher, or its instructor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "One of three, told apart by `code`. `METER_ANOMALY`: the reading looks like a typo. `MAINTENANCE_TRIGGER`: saving it would ground the aircraft. Resubmit with the matching confirm flag for either. `RESERVATION_BILLED`: money still stands against the booking, so void the invoice or reverse the ledger charge first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "correctReservationReviewTimes",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The corrected figures. Send both halves of whichever meter you are correcting.",
                "properties": {
                  "hobbsTimeOut": {
                    "type": "number"
                  },
                  "hobbsTimeIn": {
                    "type": "number"
                  },
                  "tachTimeOut": {
                    "type": "number"
                  },
                  "tachTimeIn": {
                    "type": "number"
                  },
                  "briefing": {
                    "type": "number",
                    "description": "Instruction time, on a booking measured by it."
                  },
                  "confirmMeterAnomaly": {
                    "type": "boolean"
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean",
                    "description": "Accept that saving this reading will ground the aircraft."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/reopen": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Reopen a close-out",
        "description": "Clears the sign-offs on a closed-out booking so its times, rates or cost split can be corrected, and the crew re-confirm afterwards.\n\nNothing else is undone: the readings, the crew and the booked times are left exactly as they are. This is the step that makes the existing correction endpoints reachable again on a booking everybody has already signed off.\n\n**Money comes first.** A booking with a standing invoice, or an un-reversed ledger charge, is refused with `409` and `code: \"RESERVATION_BILLED\"`. Void the invoice (or reverse the ledger charge) and call this again. A VOIDED invoice does not block it.\n\n**Narrower than correcting.** A close-out PIN is a signature, so taking one back off a flight record is limited to the organization's staff: an **admin**, a **dispatcher**, or the **instructor on this booking**. A pilot cannot clear another pilot's confirmation. Correcting a reading before anybody has signed needs no such authority. `reason` is required and is kept on the flight's audit record.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No reason given, or this booking was cancelled, or it has not been signed off yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only an admin, a dispatcher, or the instructor on this booking, can reopen a close-out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Money is still standing against this booking. Void the invoice or reverse the ledger charge first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "reopenReservationCloseOut",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Why the close-out is being reopened.",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Kept on the audit record. Required."
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/prepaid/ensure": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Send the package invoice",
        "description": "Retries raising the Stripe package invoice (or ledger debit) on a prepaid_fixed booking that is already on the calendar. Does not cancel the booking if Stripe fails. Desk staff, the instructor on the booking, or the person who created it. Demo orgs are refused.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The package invoice could not be created. The booking is still on the calendar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You cannot collect payment on this booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "ensurePrepaidInvoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ]
      }
    },
    "/reservations/{id}/prepaid/record-offline": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Record a cash or check package payment",
        "description": "Creates the package invoice if it is missing, then marks it paid out of band for cash or a check. Same people who may collect on the booking. Use this when the guest already paid at the desk, or when the package invoice never went out and they are paying by check or cash. Demo orgs are refused.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "This booking is billed after the flight, or the invoice could not be marked paid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You cannot collect payment on this booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "recordPrepaidOfflinePayment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "How they paid.",
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "check"
                    ]
                  }
                },
                "required": [
                  "method"
                ]
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/invoices": {
      "post": {
        "tags": [
          "Reservations"
        ],
        "summary": "Invoice a reservation",
        "description": "Generates the bill for a closed-out flight. Requires the **admin** role.\n\n**Returns an ARRAY.** A booking shared between several people produces one invoice PER PAYER , a Stripe invoice bills exactly one customer, so splitting a booking's cost has to be several invoices rather than one invoice with shares underneath it. A booking with one payer returns an array of one.\n\nHow the cost divides is set per organization and per booking type , see the cost-splitting rules in the console. With no rules configured, one person is billed for the whole booking, which is how this endpoint has always behaved.\n\n**Partial success is possible and is not an error.** If the payment processor accepts some invoices and rejects another, the ones that succeeded are real and are returned, with a `warnings` array naming who could not be billed. Call this endpoint again to bill only the payers who still have no invoice , it will not duplicate the ones already raised. A 400 means nothing was billed at all.\n\nA share below the processor's 50-cent minimum is skipped rather than raised, and reported in `warnings`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The flight is not closed out, every payer is already invoiced, or the organization's split rules cannot be applied to it , for example a booking set to split by each person's own time where somebody's hours have not been entered. The message says what to fix.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createInvoiceForReservation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/reservations/{id}/payers": {
      "put": {
        "tags": [
          "Reservations"
        ],
        "summary": "Set who pays what",
        "description": "Records each person's stake in the cost of a booking: their own meter readings when the charge is split by the time each person flew, their percentage when it is split by set shares, or a waiver for somebody who is on the booking but owes nothing (a safety pilot).\n\n**Replaces the whole set.** Shares have to total 100% across everybody, so merging one person into an existing set is how a total quietly stops adding up. Send everybody every time.\n\nOnly people already on the booking may be given a stake. Refused once the booking has been invoiced , the invoices describe the hours and shares they were computed from, and rewriting those underneath them would leave the money disagreeing with its own record.\n\nWhether the shares total 100%, and whether the individual meter readings add up to what the aircraft actually ran, is checked when the booking is INVOICED rather than here , so a half-finished close-out can still be saved.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reservation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A stake belongs to nobody, names somebody who isn't on the booking, has a reading that runs backwards, or the booking is already invoiced. The message says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not on this booking and are not staff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setReservationPayers",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Everyone being billed for this booking.",
                "properties": {
                  "payers": {
                    "type": "array",
                    "description": "One entry per person. Exactly one of `orgUserId` or `guestId` on each.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "orgUserId": {
                          "type": "integer",
                          "description": "The member paying."
                        },
                        "guestId": {
                          "type": "integer",
                          "description": "The non-member paying."
                        },
                        "weightBps": {
                          "type": "integer",
                          "description": "Their share in basis points , 6000 is 60%. Only used when the charge is split by set shares."
                        },
                        "hobbsOut": {
                          "type": "integer",
                          "description": "Their own Hobbs at the start of their leg, in tenths of an hour."
                        },
                        "hobbsIn": {
                          "type": "integer",
                          "description": "Their own Hobbs at the end of their leg, in tenths of an hour."
                        },
                        "tachOut": {
                          "type": "integer",
                          "description": "As `hobbsOut`, for a tach-billed aircraft."
                        },
                        "tachIn": {
                          "type": "integer",
                          "description": "As `hobbsIn`, for a tach-billed aircraft."
                        },
                        "instructionMinutes": {
                          "type": "integer",
                          "description": "Minutes of instruction attributable to this person."
                        },
                        "waived": {
                          "type": "boolean",
                          "description": "On the booking but charged nothing."
                        },
                        "waivedReason": {
                          "type": "string",
                          "description": "Why, e.g. \"Safety pilot\". Under 60 characters."
                        }
                      }
                    }
                  }
                },
                "required": [
                  "payers"
                ]
              }
            }
          }
        }
      }
    },
    "/memberships/plans": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "List membership plans",
        "description": "Every plan the organization offers, with how many members are on each. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MembershipPlan"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMembershipPlans",
        "parameters": [
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include retired plans. Defaults to false."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Create a membership plan",
        "description": "Tiers are plans , full, associate, social , so a new tier is a new plan rather than a setting. Amounts are in **cents**. Leave `duesCents` empty for a join-fee-only plan, or `joinFeeCents` empty for dues with no fee. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MembershipPlan"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The amount, cadence or billing day is not usable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createMembershipPlan",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The plan.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "joinFeeCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "One-time, in cents. Null for no join fee."
                  },
                  "joinFeeLabel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "description": "How the fee reads on the invoice."
                  },
                  "duesCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Recurring, in cents. Null for a plan with no dues."
                  },
                  "duesLabel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "duesInterval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual"
                    ]
                  },
                  "duesDayOfMonth": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 28,
                    "description": "Bill every member on this day. Capped at 28 so every month has one. Null bills each member on their own join anniversary instead."
                  },
                  "prorateFirstPeriod": {
                    "type": "boolean",
                    "description": "Charge a part-period when somebody joins mid-cycle. Only meaningful with duesDayOfMonth."
                  },
                  "autoBillDues": {
                    "type": "boolean",
                    "description": "Raise each period's invoice automatically, overnight."
                  },
                  "duesDueInDays": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Days a member gets to pay a dues or joining-fee invoice. Null means no due date. A **term of the money**, so it is snapshotted onto the membership at join."
                  },
                  "bookingWindowDays": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "How far ahead a member on this tier may book, in days. Null means no limit. An **entitlement**, so it is read live , relaxing it relaxes it for everyone immediately, unlike dues."
                  },
                  "agreementDocumentTypeId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "The document a member on this plan is expected to have on file. Recorded only , nothing is enforced on it."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/memberships/plans/options": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "Plans a member may be put on",
        "description": "Names and prices only, for a plan picker. Readable by any member.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMembershipPlanOptions",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/memberships/plans/{planId}": {
      "patch": {
        "tags": [
          "Memberships"
        ],
        "summary": "Update a membership plan",
        "description": "Including the price. This is safe by design: every membership carries its own snapshot, so changing a plan cannot re-price anybody already on it. Moving an existing member to today's price is a separate, deliberate act. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MembershipPlan"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateMembershipPlan",
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The plan id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change. Omit a field to leave it alone; send null to clear it.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "joinFeeCents": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "duesCents": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "duesInterval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual"
                    ]
                  },
                  "duesDayOfMonth": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "prorateFirstPeriod": {
                    "type": "boolean"
                  },
                  "autoBillDues": {
                    "type": "boolean"
                  },
                  "duesDueInDays": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "bookingWindowDays": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/memberships/plans/{planId}/archive": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Retire a plan, or bring it back",
        "description": "There is no delete. A plan nobody is on is still the answer to what a member was paying two years ago, so retiring it stops it being offered to new members and leaves every historical charge readable. Members already on it keep being billed. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "archiveMembershipPlan",
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The plan id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which way.",
                "properties": {
                  "archived": {
                    "type": "boolean",
                    "description": "Defaults to true."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/memberships/plans/{planId}/rates": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "A tier's aircraft rates",
        "description": "Only the aircraft this plan overrides. Anything absent bills at the aircraft's own published rate. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MembershipPlanRate"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMembershipPlanRates",
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The plan id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/memberships/plans/{planId}/rates/{resourceId}": {
      "put": {
        "tags": [
          "Memberships"
        ],
        "summary": "Set or clear a tier's rate for one aircraft",
        "description": "What members on this tier pay per hour for this aircraft, in cents. **Sending both rates as null clears the override**, returning the aircraft to its published rate.\n\nThe override applies to a booking only when **everyone being billed on it resolves to the same rate** , one pilot, or several on the same tier. Pilots on different tiers, or anyone without an active membership, fall back to the aircraft's own rate rather than being priced off somebody else's tier. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such plan or aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setMembershipPlanRate",
        "parameters": [
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The plan id."
          },
          {
            "name": "resourceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The aircraft's resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rate, in cents per hour. Wet wins over dry, matching ordinary pricing.",
                "properties": {
                  "wetRate": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "dryRate": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/memberships": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "List memberships",
        "description": "Every membership in the organization. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Membership"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMemberships",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "pending | active | suspended | cancelled"
          },
          {
            "name": "planId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter to one plan."
          },
          {
            "name": "take",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Page size, 1-200. Defaults to 50."
          },
          {
            "name": "skip",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Rows to skip."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Put a member on a plan",
        "description": "Snapshots the plan's prices onto the membership. Starts **pending** unless `start` is true , a membership that begins billing the moment it is created is rarely what the desk wants. A member may hold only one live membership at a time. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Membership"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This person already has a live membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createMembership",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The membership.",
                "properties": {
                  "orgUserId": {
                    "type": "integer"
                  },
                  "planId": {
                    "type": "integer"
                  },
                  "start": {
                    "type": "boolean",
                    "description": "Start it now rather than leaving it pending."
                  },
                  "startedAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Backdate the start. Defaults to now."
                  },
                  "waiveJoinFee": {
                    "type": "boolean",
                    "description": "Join with no join fee , a founding member, or a transfer from another club."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "orgUserId",
                  "planId"
                ]
              }
            }
          }
        }
      }
    },
    "/memberships/me": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "The caller's own membership",
        "description": "What the caller is on, what it costs, when the next period lands, and what they have been billed. Answers **200 with null** when they are not on a plan, which is the ordinary answer at an organization that does not run memberships. Readable by any member.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyMembership"
      }
    },
    "/memberships/vocabulary": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "Cadences and statuses",
        "description": "The billing cadences and membership statuses this server accepts. A vocabulary, not a permission , any member may read it.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMembershipVocabulary"
      }
    },
    "/memberships/orgUser/{orgUserId}": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "One member's membership",
        "description": "The live membership for a member, or the most recent one if it has ended. Null when they have never had one. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMembershipForMember",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          }
        ]
      }
    },
    "/memberships/{membershipId}": {
      "get": {
        "tags": [
          "Memberships"
        ],
        "summary": "Get a membership",
        "description": "With its recent dues periods and what the next billable period would be. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Membership"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMembership",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Memberships"
        ],
        "summary": "Update a membership",
        "description": "Everything that does not change what it costs. `agreementOnFile` records that the member's paperwork is in , it is a record, never a gate, and nothing is blocked when it is missing. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Membership"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateMembership",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "autoBillDues": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "agreementOnFile": {
                    "type": "boolean"
                  },
                  "agreementDocumentId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Must be a document belonging to this member."
                  },
                  "nextDueAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Move the billing cursor. The escape hatch for a period settled outside the system."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/memberships/{membershipId}/status": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Start, pause, resume or end a membership",
        "description": "Only an **active** membership accrues dues. Pausing keeps the row and stops the meter, so a member away for the winter does not come back months in arrears. **Cancelled is terminal** , bringing somebody back is a new membership, because reviving one would resume a cursor that has been sitting still and bill every period since. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "That transition is not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setMembershipStatus",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new status.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "active",
                      "suspended",
                      "cancelled"
                    ]
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        }
      }
    },
    "/memberships/{membershipId}/plan": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Move a member to a different plan",
        "description": "Takes the new plan's current prices, **from the next period**. Anything already invoiced is untouched. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The membership has ended, or the plan is retired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "changeMembershipPlan",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new plan.",
                "properties": {
                  "planId": {
                    "type": "integer"
                  }
                },
                "required": [
                  "planId"
                ]
              }
            }
          }
        }
      }
    },
    "/memberships/{membershipId}/joinFee": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Bill the join fee",
        "description": "Raises an ordinary invoice for the one-time fee, at the amount snapshotted when the member joined. Billable **once**. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "There is no join fee, or it has already been invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "billMembershipJoinFee",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      }
    },
    "/memberships/{membershipId}/dues": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Bill the next dues period",
        "description": "**Which** period is decided by the server from the membership's own cursor and is deliberately not accepted from the request , a client that could name the period could bill the same month twice. An outstanding part-period from joining mid-cycle is billed first, at its own price. A period that has already been billed answers 409 rather than raising a second invoice. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nothing is owed, or that period has already been billed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "billMembershipDues",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ]
      }
    },
    "/memberships/{membershipId}/dues/skip": {
      "post": {
        "tags": [
          "Memberships"
        ],
        "summary": "Waive the next dues period",
        "description": "Records the period as deliberately not charged , a comped month, or a leave of absence , so nothing comes back for it later. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "There are no dues to waive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "skipMembershipDues",
        "parameters": [
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Why.",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/courses": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List courses",
        "description": "The courses your organization teaches. Requires the **configureTraining** grant or the **admin** role , a syllabus is not students' business.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCourses",
        "parameters": [
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include retired courses."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Create a course",
        "description": "Creates the course and its first editable draft version, `Rev A`. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createCourse",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The course.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "enrollmentFeeCents": {
                    "type": "integer",
                    "description": "What the organization charges to enroll, in cents. Omit for a free course."
                  },
                  "enrollmentFeeLabel": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "How the fee reads on the invoice line."
                  },
                  "regulatoryPart": {
                    "type": "string",
                    "enum": [
                      "part61",
                      "part141"
                    ],
                    "description": "Which regime this course is trained under. Defaults to `part61`. Cannot be changed later , an organization moving to Part 141 creates a new course."
                  },
                  "certificateSought": {
                    "type": "string",
                    "description": "private | instrument | commercial | cfi | atp | endorsement | other."
                  },
                  "ratingId": {
                    "type": "integer",
                    "description": "An instruction rate to price this course from. Pricing only , a rating is a rate card, not the course."
                  },
                  "targetDays": {
                    "type": "integer",
                    "description": "How many days the organization plans this course to take. Feeds the pace on a student's record: with no target, nobody is ever behind. Omit for no plan."
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/training/templates": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List syllabus templates",
        "description": "Ready-made syllabi an organization can fork. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCurriculumTemplates",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/training/templates/{templateKey}": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Create a course from a template",
        "description": "Builds a complete course , stages, lessons, ACS-coded tasks and requirements, with the credit map already wired , as an editable **draft**. Templates are always created as Part 61: a template cannot be an FAA-approved Training Course Outline, because approval is per organization and per FSDO. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createCourseFromTemplate",
        "parameters": [
          {
            "name": "templateKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The template key, e.g. `ppl-asel-part61`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional overrides.",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Defaults to the template's name."
                  },
                  "ratingId": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/courses/{courseId}": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "Get a course",
        "description": "The course and every version of its syllabus. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCourse",
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The course id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Training"
        ],
        "summary": "Update a course",
        "description": "Requires the **configureTraining** grant or the **admin** role. `regulatoryPart` cannot be changed.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateCourse",
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The course id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "certificateSought": {
                    "type": "string"
                  },
                  "ratingId": {
                    "type": "integer"
                  },
                  "targetDays": {
                    "type": "integer",
                    "description": "How many days the organization plans this course to take, or null for no plan."
                  },
                  "archived": {
                    "type": "boolean",
                    "description": "Archive the course. It stops appearing on the Training page and cannot be enrolled on; existing students are untouched. Reversible."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/courses/{courseId}/versions": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Create a syllabus version",
        "description": "Pass `copyFromVersionId` to fork an existing version , the normal way to revise a published syllabus, since the published one cannot be edited. The copy is deep and the new version is an editable draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A version with that label already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createCourseVersion",
        "parameters": [
          {
            "name": "courseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The course id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The version.",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "Free text, e.g. \"Rev B\" , organizations match what they filed with their FSDO."
                  },
                  "copyFromVersionId": {
                    "type": "integer"
                  }
                },
                "required": [
                  "label"
                ]
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "Get a syllabus version",
        "description": "The whole syllabus: stages, lessons, tasks and requirements. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCourseVersion",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ]
      }
    },
    "/training/versions/{versionId}/publish": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Publish a syllabus version",
        "description": "**Freezes the version permanently.** After this, no stage, lesson, task or requirement under it can be changed , students are enrolled against exactly these lessons. To revise, create a new version from this one. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The version has no lessons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "publishCourseVersion",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional FSDO approval details, for Part 141.",
                "properties": {
                  "approvedAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "approvalReference": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/retire": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Retire a syllabus version",
        "description": "Stops new enrollments. Students already on it finish on it. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "retireCourseVersion",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Set `retired` false to un-retire.",
                "properties": {
                  "retired": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/stages": {
      "put": {
        "tags": [
          "Training"
        ],
        "summary": "Create or update a stage",
        "description": "Omit `stageId` to create. Only works on a draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published and cannot be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "upsertCourseStage",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The stage.",
                "properties": {
                  "stageId": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "objective": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer"
                  },
                  "requiresStageCheck": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "position"
                ]
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/stages/{stageId}": {
      "delete": {
        "tags": [
          "Training"
        ],
        "summary": "Delete a stage",
        "description": "Only works on a draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist on it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteCourseStage",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          },
          {
            "name": "stageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The stage id."
          }
        ]
      }
    },
    "/training/versions/{versionId}/lessons": {
      "put": {
        "tags": [
          "Training"
        ],
        "summary": "Create or update a lesson",
        "description": "Omit `lessonId` to create. `credits` declares what this lesson is designed to credit and from which clock , this is what makes one flight post to several requirements at once. Only works on a draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist and the credit map, sign-off, notes, or stage-check flag would change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "upsertSyllabusLesson",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The lesson.",
                "properties": {
                  "lessonId": {
                    "type": "integer"
                  },
                  "stageId": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "position": {
                    "type": "integer"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "ground",
                      "flight",
                      "sim"
                    ]
                  },
                  "objectives": {
                    "type": "string"
                  },
                  "completionStandards": {
                    "type": "string"
                  },
                  "minFlightDeciHours": {
                    "type": "integer",
                    "description": "Tenths of an hour."
                  },
                  "minGroundDeciHours": {
                    "type": "integer",
                    "description": "Tenths of an hour."
                  },
                  "requiresSignoff": {
                    "type": "boolean",
                    "description": "Must an instructor sign this lesson before it counts? Defaults true."
                  },
                  "requiresNotes": {
                    "type": "boolean",
                    "description": "Must the instructor write something before signing? Defaults false."
                  },
                  "isStageCheck": {
                    "type": "boolean",
                    "description": "Is this the stage's §141.37 stage check? Signing one requires the `checkInstructor` designation for this course, and the signer must not be the student's own instructor. Defaults false."
                  },
                  "credits": {
                    "type": "array",
                    "description": "Which requirements this lesson credits.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "requirementId": {
                          "type": "integer"
                        },
                        "creditFrom": {
                          "type": "string",
                          "enum": [
                            "flight",
                            "instruction",
                            "count"
                          ]
                        }
                      }
                    }
                  }
                },
                "required": [
                  "stageId",
                  "name",
                  "position",
                  "kind"
                ]
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/lessons/{lessonId}": {
      "delete": {
        "tags": [
          "Training"
        ],
        "summary": "Delete a lesson",
        "description": "Only works on a draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist on it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteSyllabusLesson",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The lesson id."
          }
        ]
      }
    },
    "/training/versions/{versionId}/lessons/{lessonId}/tasks": {
      "put": {
        "tags": [
          "Training"
        ],
        "summary": "Replace a lesson's tasks",
        "description": "Send the full list; it replaces what is there. `acsCode` maps a task to the ACS, e.g. `PA.VI.A`. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist on the tasks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setLessonTasks",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          },
          {
            "name": "lessonId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The lesson id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The full task list.",
                "properties": {
                  "tasks": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "position": {
                          "type": "integer"
                        },
                        "acsCode": {
                          "type": "string"
                        },
                        "standard": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "required": [
                  "tasks"
                ]
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/requirements": {
      "put": {
        "tags": [
          "Training"
        ],
        "summary": "Create or update a requirement",
        "description": "What a student must accumulate , 35 hours total, 3 hours night, 10 towered landings. Measured in hours **or** a count, never both. `source` is `part61`, `part141` or `organization`; only FAA-sourced ones can block a Part 141 graduation. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Measured in hours or events, never both, and never neither.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or the code is already used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "upsertCourseRequirement",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The requirement.",
                "properties": {
                  "requirementId": {
                    "type": "integer"
                  },
                  "code": {
                    "type": "string",
                    "description": "Stable machine key, e.g. \"total_flight\", \"night\", \"cross_country\"."
                  },
                  "label": {
                    "type": "string"
                  },
                  "minDeciHours": {
                    "type": "integer",
                    "description": "Tenths of an hour."
                  },
                  "minCount": {
                    "type": "integer"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "part61",
                      "part141",
                      "school"
                    ]
                  },
                  "maxSimulatorBps": {
                    "type": "integer",
                    "description": "Ceiling on simulator credit, in basis points. Appendix B allows 2000 (20%)."
                  },
                  "maxTransferBps": {
                    "type": "integer",
                    "description": "Ceiling on transferred credit, in basis points. §141.77 allows 5000 from an approved course, 2500 otherwise."
                  },
                  "recencyCalendarMonths": {
                    "type": "integer",
                    "description": "Training older than this many CALENDAR months stops counting toward this requirement. §61.109(a)(4) wants the three hours of test preparation within 2. Omit or send null for a requirement with no window , but note that omitting it on an update CLEARS an existing window."
                  }
                },
                "required": [
                  "code",
                  "label"
                ]
              }
            }
          }
        }
      }
    },
    "/training/versions/{versionId}/requirements/{requirementId}": {
      "delete": {
        "tags": [
          "Training"
        ],
        "summary": "Delete a requirement",
        "description": "Only works on a draft. Requires the **configureTraining** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist on it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteCourseRequirement",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          },
          {
            "name": "requirementId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The requirement id."
          }
        ]
      }
    },
    "/training/enrollments": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List enrollments",
        "description": "Staff see everyone's. **A student always sees only their own**, whatever they ask for , §141.101 gives them the right to their record, and no right to anyone else's.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listEnrollments",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter to one student. Ignored for callers who are not staff."
          },
          {
            "name": "courseId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Filter to one course."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "enrolled | graduated | terminated | transferred"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Enroll a student",
        "description": "Pins the student to this syllabus version. A Part 141 course must be published first. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already enrolled, or the version is a draft, retired, or the course or member is archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "enrollStudent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The enrollment.",
                "properties": {
                  "versionId": {
                    "type": "integer"
                  },
                  "orgUserId": {
                    "type": "integer"
                  },
                  "enrollmentCertificateNumber": {
                    "type": "string",
                    "description": "§141.93 certificate of enrollment."
                  }
                },
                "required": [
                  "versionId",
                  "orgUserId"
                ]
              }
            }
          }
        }
      }
    },
    "/training/enrollments/{enrollmentId}": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "A student's progress",
        "description": "The syllabus, every graded record against it, and where the ledger stands on each requirement , including what a simulator or transfer ceiling disallowed. Readable by the student themselves, instructors, admins, and anyone granted `manageEnrollment`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getEnrollmentProgress",
        "parameters": [
          {
            "name": "enrollmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The enrollment id."
          }
        ]
      }
    },
    "/training/enrollments/{enrollmentId}/graduate": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Graduate a student",
        "description": "Under Part 141 this refuses while any FAA-sourced requirement is unmet, and requires the chief instructor to have certified the record. Under Part 61 nothing blocks , the instructor decides readiness. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Requirements are unmet, or the enrollment has already ended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "graduateStudent",
        "parameters": [
          {
            "name": "enrollmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The enrollment id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional.",
                "properties": {
                  "graduationCertificateNumber": {
                    "type": "string",
                    "description": "§141.95 graduation certificate."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/enrollments/{enrollmentId}/end": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Terminate or transfer an enrollment",
        "description": "§141.101 wants the date and the reason. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The enrollment has already ended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "endEnrollment",
        "parameters": [
          {
            "name": "enrollmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The enrollment id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "How it ended.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "terminated",
                      "transferred"
                    ]
                  },
                  "reason": {
                    "type": "string"
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        }
      }
    },
    "/training/enrollments/{enrollmentId}/certify": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Certify a training record",
        "description": "§141.85 , the chief instructor certifying the record before graduation. Requires the **checkInstructor** designation for this course, or the **admin** role. The record stays amendable after certify.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The enrollment is closed, or the record is already certified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "certifyEnrollment",
        "parameters": [
          {
            "name": "enrollmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The enrollment id."
          }
        ]
      }
    },
    "/training/grants/catalog": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List the training grants on offer",
        "description": "The four grants an organization can hand out, with a description of each. Any member may read this , it is a vocabulary, not a permission.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listTrainingGrantCatalog",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/training/grants/mine": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "What the caller may do in training",
        "description": "The grants held by the caller, the ones their role implies, and whether they may grade. Every client needs this to decide which buttons to offer.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyTrainingGrants"
      }
    },
    "/training/grants": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List training grants",
        "description": "Every grant in the organization. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listTrainingGrants",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Grant a training permission",
        "description": "Gives someone a training grant. `courseId` is only accepted for `checkInstructor`, which §141.37 designates per approved course. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createTrainingGrant",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The grant.",
                "properties": {
                  "orgUserId": {
                    "type": "integer"
                  },
                  "grant": {
                    "type": "string",
                    "enum": [
                      "configureTraining",
                      "manageEnrollment",
                      "checkInstructor",
                      "auditor"
                    ]
                  },
                  "courseId": {
                    "type": "integer",
                    "description": "Only for checkInstructor. Omit for an organization-wide designation."
                  }
                },
                "required": [
                  "orgUserId",
                  "grant"
                ]
              }
            }
          }
        }
      }
    },
    "/training/grants/{grantId}": {
      "delete": {
        "tags": [
          "Training"
        ],
        "summary": "Revoke a training grant",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "revokeTrainingGrant",
        "parameters": [
          {
            "name": "grantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The grant id."
          }
        ]
      }
    },
    "/training/enrollments/{enrollmentId}/fee": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Bill the course enrollment fee",
        "description": "Raises an invoice, or a ledger charge if the organization bills that way, for the fee this enrollment snapshotted when the student enrolled. Refuses if the course had no fee or the fee has already been billed , a second charge for the same fee is not something a refund undoes cleanly. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "invoiceId": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Set when invoicing raised a Stripe invoice."
                        },
                        "ledgerEntryId": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Set when the fee posted to the member ledger instead."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Stripe or billing configuration refused the charge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "There is no fee on this enrollment, or it has already been billed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "billEnrollmentFee",
        "parameters": [
          {
            "name": "enrollmentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The enrollment id."
          }
        ]
      }
    },
    "/training/records": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Grade a lesson",
        "description": "Creates or updates a training record. Editable until it is signed and **frozen the moment it is**. A passing grade on a lesson the syllabus marked sign-off optional also signs and posts credits (`signed: true` on the response), because the console then treats the lesson as complete and hides Sign. Pass `reservationId` to tie it to the booking it came from. Requires the **instructor** or **admin** role.",
        "responses": {
          "200": {
            "description": "Saved. `warning` carries a Part 141 out-of-order notice, if any. `signed` is true when a passing grade on a sign-off-optional lesson also posted its credits.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The record is already signed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "saveLessonRecord",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The grade.",
                "properties": {
                  "enrollmentId": {
                    "type": "integer"
                  },
                  "lessonId": {
                    "type": "integer"
                  },
                  "recordId": {
                    "type": "integer",
                    "description": "Omit to create."
                  },
                  "grade": {
                    "type": "string",
                    "description": "From the course version's grading scale. Defaults to S / U / I."
                  },
                  "notes": {
                    "type": "string"
                  },
                  "flightDeciHours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Tenths. Normally the Hobbs delta. Omit on an update to leave the existing figure; send null to clear it."
                  },
                  "instructionDeciHours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Tenths. Normally the briefing time. Omit on an update to leave the existing figure; send null to clear it."
                  },
                  "simulatorDeciHours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Tenths. The part flown in a simulator or FTD. Omit on an update to leave the existing figure; send null to clear it."
                  },
                  "reservationId": {
                    "type": "integer"
                  },
                  "instructorOrgUserId": {
                    "type": "integer",
                    "description": "Defaults to the caller."
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the training happened. The phone sends this from a grade queued offline so recency is judged on the flying, not on the sync. A linked booking's start still wins. Defaults to now."
                  },
                  "taskGrades": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "lessonTaskId": {
                          "type": "integer"
                        },
                        "grade": {
                          "type": "string"
                        },
                        "notes": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "required": [
                  "enrollmentId",
                  "lessonId"
                ]
              }
            }
          }
        }
      }
    },
    "/training/records/{recordId}/sign": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Sign a lesson",
        "description": "**Freezes the record and posts what it earned to the ledger, atomically.** By default the credits come from what the syllabus says the lesson credits; pass `credits` to override , weather turns a night cross-country into a local day flight, and the record has to be able to say so. A second Sign on an already-signed record is a no-op (200 with the existing `creditsPosted`), so save-then-sign after a sign-off-optional save does not fail. Requires the **instructor** or **admin** role.",
        "responses": {
          "200": {
            "description": "Signed. `creditsPosted` is how many ledger entries it wrote.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not graded yet, or the enrollment is closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "signLessonRecord",
        "parameters": [
          {
            "name": "recordId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The record id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional credit override.",
                "properties": {
                  "credits": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "requirementId": {
                          "type": "integer"
                        },
                        "deciHours": {
                          "type": "integer"
                        },
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the training happened. Same field as grade. A linked booking's start still wins."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/training/records/{recordId}/countersign": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Countersign a lesson",
        "description": "The student's signature on what their instructor recorded. Only the student on the record may call it, and only after the instructor has signed.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not your record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The instructor has not signed yet, or this lesson does not ask for a student signature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "countersignLessonRecord",
        "parameters": [
          {
            "name": "recordId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The record id."
          }
        ]
      }
    },
    "/training/records/{recordId}/amend": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Amend a signed lesson",
        "description": "A signed record is never edited. This writes a **new record superseding it** and reverses everything the original posted to the ledger. Both records stay, and so do both sets of ledger entries. Requires the **instructor** or **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not signed, or already amended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "amendLessonRecord",
        "parameters": [
          {
            "name": "recordId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The record id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Why.",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Stays on the record and on every reversing entry."
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        }
      }
    },
    "/training/credits": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Post credit by hand",
        "description": "For transferred training and backfills. Measured in hours **or** a count, never both, and never negative , to take credit back, reverse the entry that granted it. A `manual` credit must carry a note. Lesson hours are posted when the instructor signs, not through this endpoint. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The entry would be meaningless or unexplained, a transfer is missing when it was flown, or the source is a signed lesson (those are posted by signing).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The enrollment is closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "postRequirementCredit",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The entry.",
                "properties": {
                  "enrollmentId": {
                    "type": "integer"
                  },
                  "requirementId": {
                    "type": "integer"
                  },
                  "deciHours": {
                    "type": "integer",
                    "description": "Tenths."
                  },
                  "count": {
                    "type": "integer"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "transfer_141",
                      "transfer_61",
                      "simulator",
                      "manual"
                    ]
                  },
                  "notes": {
                    "type": "string",
                    "description": "Required for `manual`."
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the training actually happened. Required for transferred hours (`transfer_61`, `transfer_141`): recency is judged on this date, not on when the row was written. Defaults to now for simulator and manual credits."
                  }
                },
                "required": [
                  "enrollmentId",
                  "requirementId",
                  "source"
                ]
              }
            }
          }
        }
      }
    },
    "/training/credits/{creditId}/reverse": {
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Reverse a ledger entry",
        "description": "Writes a negative entry alongside. Nothing is deleted, and a reversal cannot itself be reversed. Hours posted by a signed lesson are taken back by amending the lesson, not here. Requires the **manageEnrollment** grant or the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already reversed, itself a reversal, or the hours came from a signed lesson.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "reverseRequirementCredit",
        "parameters": [
          {
            "name": "creditId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The entry id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Why.",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        }
      }
    },
    "/training/endorsements": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "List endorsements",
        "description": "A pilot's signed endorsements. Readable by the person they belong to and by instructors and admins , **a member always sees only their own**, whatever they ask for. Superseded ones are hidden unless asked for: a renewed 90-day solo replaces the earlier one, and listing both invites signing against the wrong row.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listEndorsements",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Whose. Ignored for callers who are not staff."
          },
          {
            "name": "includeSuperseded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include replaced endorsements."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Training"
        ],
        "summary": "Sign an endorsement",
        "description": "The `renderedText` you send is stored **verbatim and never regenerated**. AC 61-65K replaced AC 61-65H and will itself be replaced; an endorsement has to keep saying what was signed on the day it was signed. Pass `supersedesId` to replace an earlier one , a renewed 90-day solo is a new endorsement, not an edit. Requires the **instructor** or **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That endorsement has already been replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createEndorsement",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The endorsement.",
                "properties": {
                  "orgUserId": {
                    "type": "integer",
                    "description": "Who it is for."
                  },
                  "templateCode": {
                    "type": "string",
                    "description": "AC 61-65K paragraph, e.g. `A.3`. Optional; recorded for reference."
                  },
                  "title": {
                    "type": "string"
                  },
                  "renderedText": {
                    "type": "string",
                    "description": "The endorsement exactly as signed, with every blank filled in."
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Defaults from the template , 90 days for a solo endorsement. Omit for one that does not expire."
                  },
                  "enrollmentId": {
                    "type": "integer",
                    "description": "Optional. Plenty of endorsements belong to a person rather than a course."
                  },
                  "signerCertificateNumber": {
                    "type": "string"
                  },
                  "signerCertificateExpiry": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "supersedesId": {
                    "type": "integer"
                  }
                },
                "required": [
                  "orgUserId",
                  "renderedText"
                ]
              }
            }
          }
        }
      }
    },
    "/training/endorsements/templates": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "Endorsement templates",
        "description": "The AC 61-65K Appendix A endorsements a Part 61/141 flight school actually signs. Pass `orgUserId` and the student's name is substituted for you; every other `{placeholder}` is left for the instructor, because inventing values would be putting words into a signature.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listEndorsementTemplates",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Pre-fill the student's name."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/training/versions/{versionId}/gradingScale": {
      "put": {
        "tags": [
          "Training"
        ],
        "summary": "Set the grading scale",
        "description": "Declares this course's marks and which of them mean a lesson is complete. `\"3\"` passes at an organization marking 1,4 and fails at one marking 1,3, so guessing from the string is how a gradebook quietly disagrees with the organization using it. Draft versions only. Requires the **configureTraining** grant or the **admin** role.\n\n**The scale is written as rows and read back as two fields.** Anywhere a course version is returned, `gradingScale` is the list of codes in display order and `gradeOptions` is the same list with each mark's `passing` flag. Build a grade picker from `gradingScale`, and read `gradeOptions` when you need to know what a mark means.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Every grade cannot fail, and two grades cannot share a name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The version is published, or training records exist on it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setGradingScale",
        "parameters": [
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The full scale, in display order.",
                "properties": {
                  "scale": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "passing": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                },
                "required": [
                  "scale"
                ]
              }
            }
          }
        }
      }
    },
    "/training/candidates": {
      "get": {
        "tags": [
          "Training"
        ],
        "summary": "Which lesson is this booking?",
        "description": "The lessons a booking could be closing out, unfinished ones first, filtered to what the booking type can be , a `dual` booking offers flight lessons, `ground` offers ground. A rental or a guest flight offers nothing.",
        "responses": {
          "200": {
            "description": "One entry per enrollment. Each lesson carries its ACS `tasks` (`id`, `name`, `acsCode`, `standard`), which are what `taskGrades` on a saved record refer to; `complete` is true only for a signed passing grade (a U is not complete); `recordId` is the unsigned draft for that lesson, if there is one, so a close-out can update it rather than creating a second row; `draft` is that row's grade, notes, hours, task marks and `reservationId` (the booking already linked, if any) so the close-out can show what was already written rather than overwriting it, and so it can refuse to Sign a draft that belongs to a different flight; `gradedReservationIds` are the bookings that already have a signed record on this enrollment; and `gradedOn` names the lesson signed on each of those bookings, so a reload of the close-out can still say this flight was graded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCandidateLessons",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "The student."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The booking type: dual, solo, ground or sim. Omit for every lesson."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/organizations/splitRules": {
      "get": {
        "tags": [
          "Cost splitting"
        ],
        "summary": "Your cost-splitting rules",
        "description": "How this organization divides a booking's cost when more than one person is on it. Requires the **admin** role.\n\nReturns the stored rules, the effective rule for every bookable type (`resolved`), the vocabulary this server accepts (`apportionments`, `chargeLines`, `bookableTypes`, `personnelLimits`), human copy for each option, the four starting-point `presets`, and worked `examples` computed by the actual billing engine , so an integration can show a customer what a rule will do without reimplementing the arithmetic.\n\nThe rules table is SPARSE. An organization with no rules divides nothing: one person is billed for the whole booking, which is how AerScheduler has always behaved.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The rules, the resolved plan per booking type, the vocabulary, and worked examples."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getSplitRules"
      },
      "put": {
        "tags": [
          "Cost splitting"
        ],
        "summary": "Set a cost-splitting rule",
        "description": "Sets how one charge divides, for one booking type or as the organization-wide default. Requires the **admin** role.\n\nSend `apportionment: null` to REMOVE the rule and return that charge to the product default. Absence is meaningful here , an explicit `whole` is not the same thing, because it would survive a later change to what the default is.\n\nOmit `reservationType` (or send null) to set the organization-wide default for that charge.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The full rule set again , one rule can change what several booking types resolve to."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown charge, unknown apportionment, or a booking type that only ever has one person paying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setSplitRule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rule to set.",
                "properties": {
                  "reservationType": {
                    "type": "string",
                    "description": "Which booking type, e.g. `ground`. Omit for the organization-wide default."
                  },
                  "chargeLine": {
                    "type": "string",
                    "description": "`aircraft` (machine or room time) or `instruction`."
                  },
                  "apportionment": {
                    "type": "string",
                    "description": "`whole` (one person pays), `equal` (divided evenly), `measured` (each pays their own logged time), `full_to_each` (each pays the full amount , this MULTIPLIES what you collect), or `weighted` (set shares). Null removes the rule."
                  }
                },
                "required": [
                  "chargeLine"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Cost splitting"
        ],
        "summary": "Clear all cost-splitting rules",
        "description": "Removes every rule, returning the organization to one invoice per booking. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The now-empty rule set."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "clearSplitRules"
      }
    },
    "/organizations/splitRules/preset": {
      "post": {
        "tags": [
          "Cost splitting"
        ],
        "summary": "Apply a starting point",
        "description": "Replaces the organization's rules with the defaults for a kind of operation. Requires the **admin** role.\n\nReplaces rather than merges: a preset is a statement about the whole shape of an operation, and merging would leave you half-configured as one thing and half as another with no way to tell which rule came from where. `GET` first to see exactly which rules a preset writes.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "The rule set the preset wrote."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not one of the four presets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "applySplitPreset",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which kind of operation you run.",
                "properties": {
                  "preset": {
                    "type": "string",
                    "description": "`flight_school`, `flying_club`, `fbo` or `partnership`."
                  }
                },
                "required": [
                  "preset"
                ]
              }
            }
          }
        }
      }
    },
    "/availability/me": {
      "get": {
        "tags": [
          "Availability"
        ],
        "summary": "Your free windows",
        "description": "When you are free in a window, after bookings are subtracted.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Availability"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyAvailability",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "reservationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "When editing this booking, treat it as free so its own slot still appears."
          },
          {
            "name": "applyBookingPolicy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true (default), clip free windows by minimum notice and booking horizon."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Airport location for weekly hours and booking-policy time zone."
          },
          {
            "name": "bookingOnBehalf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true and the caller is staff, skip notice/horizon clipping (dispatch scheduling)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/availability/user/{userId}": {
      "get": {
        "tags": [
          "Availability"
        ],
        "summary": "A person's free windows",
        "description": "Used to find an instructor's open slots.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Availability"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserAvailability",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The user id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "reservationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "When editing this booking, treat it as free so its own slot still appears."
          },
          {
            "name": "applyBookingPolicy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true (default), clip free windows by minimum notice and booking horizon."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Airport location for weekly hours and booking-policy time zone."
          },
          {
            "name": "bookingOnBehalf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true and the caller is staff, skip notice/horizon clipping (dispatch scheduling)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/availability/resource/{id}": {
      "get": {
        "tags": [
          "Availability"
        ],
        "summary": "A resource's free windows",
        "description": "Used to find an aircraft's open slots before booking.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Availability"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getResourceAvailability",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "reservationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "When editing this booking, treat it as free so its own slot still appears."
          },
          {
            "name": "applyBookingPolicy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true (default), clip free windows by minimum notice and booking horizon."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Airport location for weekly hours and booking-policy time zone."
          },
          {
            "name": "bookingOnBehalf",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true and the caller is staff, skip notice/horizon clipping (dispatch scheduling)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/resources/aircraft": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List aircraft",
        "description": "Only resources whose type is an aircraft. Defaults to the aircraft you OPERATE: customer aircraft in your shop are excluded unless you ask for them with `scope`, and asking requires an owner, admin, dispatcher or technician role.",
        "responses": {
          "200": {
            "description": "The aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAircraft",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "grounded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on grounded state."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated location ids."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "fleet",
                "shop",
                "all"
              ]
            },
            "description": "`fleet` (default), `shop` for customer aircraft in for maintenance, or `all`. Anything unrecognised reads as `fleet`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/resources/simulators": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List simulators",
        "description": "Only resources whose type is a simulator.",
        "responses": {
          "200": {
            "description": "The simulators.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listSimulators",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "grounded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on grounded state."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated location ids."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/resources/rooms": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List rooms",
        "description": "Only resources whose type is a room.",
        "responses": {
          "200": {
            "description": "The rooms.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listRooms",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated location ids."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/resources/{id}/fuel": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Record fuel on board",
        "description": "Updates fuel for one or both engines.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateFuel",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fuel figures, in the aircraft's configured units.",
                "properties": {
                  "fuelEngine1": {
                    "type": "integer"
                  },
                  "fuelEngine2": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/resources/{id}/grounding": {
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Ground an aircraft or return it to service",
        "description": "Takes a tail off the line, or puts it back. Requires the **admin** or **technician** role.\n\nIts own endpoint rather than a field on `PATCH /resources/{id}`, which is admin-only: a technician signs maintenance off and has to be able to release the aircraft afterwards.\n\nGrounding requires a `reason`, which is shown wherever the aircraft appears. The reason you send is treated as a PERSON'S decision and is never cleared automatically. Holds the system applies itself (an inspection coming due, an open grounding squawk) are released on their own once nothing is outstanding; yours are not, and stay until someone sends `grounded: false` here.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Resource"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`grounded` missing, or grounding without a reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin or technician, or the resource belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setResourceGrounding",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which way, and why.",
                "properties": {
                  "grounded": {
                    "type": "boolean",
                    "description": "`true` takes it off the line, `false` returns it to service."
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Required when grounding. Ignored when returning to service."
                  }
                },
                "required": [
                  "grounded"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}/approvedUsers": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List who is approved on a resource",
        "description": "The members checked out to book this aircraft , the read in the opposite direction to `GET /users/{userId}/approvedResources`. Use this rather than asking every member what they are approved for.",
        "responses": {
          "200": {
            "description": "The approved members.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrganizationUser"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listResourceApprovedUsers",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "student",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **student** role. Combines as OR with `renter`."
          },
          {
            "name": "renter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Set `true` to include members holding the **renter** role. Combines as OR with `student`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/resources/{id}/approve": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Approve a member for a resource",
        "description": "Checks someone out on an aircraft. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That person is not in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "approveResource",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Who to approve.",
                "properties": {
                  "userId": {
                    "type": "integer"
                  }
                },
                "required": [
                  "userId"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}/unapprove": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Withdraw a member's approval",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That person is not in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "unapproveResource",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Whose approval to withdraw.",
                "properties": {
                  "userId": {
                    "type": "integer"
                  }
                },
                "required": [
                  "userId"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}/owners": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List an aircraft's owners",
        "description": "Who owns this aeroplane: nobody (the organization's own), a leaseback owner, or the customer whose aircraft is in the shop. Ownership decides who is invoiced for maintenance work; it decides nothing about scheduling, which is `use`. **Owners, admins, dispatchers and technicians only**, because the rows carry a person's private phone number and email.",
        "responses": {
          "200": {
            "description": "The owners, primary first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listResourceOwners",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Add an owner to an aircraft",
        "description": "Send `orgUserId` for somebody who is already a member, or `name` (plus optional `email` and `phone`) for an owner the organization has never met. The second form creates a membership for them that is marked external and left unclaimed: they appear on the roster and can be invoiced, they get none of the organization's notifications, and they cannot sign in. An invoice you raise for them IS emailed to `email`, by Stripe. `email` must be a real address shape. If somebody already holds that address, the answer is **409** naming them: send their `orgUserId` to use them, or repeat the request with `createAnyway: true` if this really is a different person. The same name at the same address is re-used without a 409, which is one customer's second aircraft. When the owner later signs up with a verified address and the same name, joining the organization claims this record rather than creating a second one. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "That person is already an owner of this aircraft, or the email address is not a real address shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Somebody already holds that email address. The body names them in `conflict` (`kind`, `orgUserId`, `name`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addResourceOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Either an existing member, or a new owner's details.",
                "properties": {
                  "orgUserId": {
                    "type": "integer",
                    "description": "An existing member of this organization."
                  },
                  "name": {
                    "type": "string",
                    "description": "The owner's name, when they are not a member."
                  },
                  "email": {
                    "type": "string",
                    "description": "Where to reach them, and where Stripe sends their invoices. Never used to sign in."
                  },
                  "phone": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string",
                    "description": "What they are to the aircraft: \"Co-owner\", \"Operator\"."
                  },
                  "isPrimary": {
                    "type": "boolean",
                    "description": "Who gets the invoice. At most one per aircraft."
                  },
                  "createAnyway": {
                    "type": "boolean",
                    "description": "After a 409: this is a different person from the one already holding the address. Create them anyway."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/resources/{id}/owners/{ownerId}": {
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Change an owner, or correct who they are",
        "description": "Rename their title, or make them the one who gets the invoice. For an owner who has not signed up, you can also correct `name`, `email` and `phone`: these belong to the PERSON, so a correction applies to every aircraft they own and to invoices raised from now on, and the address on their Stripe customer is corrected too. A member's name and contact details are theirs to change from their own profile, and are refused here. An address somebody else already holds is refused, naming them. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Owner not found, a member's details, a malformed address, or an address somebody else holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateResourceOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "ownerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The owner row's id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What to change.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "isPrimary": {
                    "type": "boolean"
                  },
                  "name": {
                    "type": "string",
                    "description": "Owners who have not signed up only."
                  },
                  "email": {
                    "type": "string",
                    "description": "Owners who have not signed up only. Must be a real address shape."
                  },
                  "phone": {
                    "type": "string",
                    "description": "Owners who have not signed up only."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Stop listing somebody as an owner",
        "description": "Removes the link only. The person stays on the roster with their invoices and history intact, because a mistyped name must not be a way to delete a customer. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Owner not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "removeResourceOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "ownerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The owner row's id."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/ownedAircraft": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List the aircraft somebody owns",
        "description": "The other direction of an aircraft's owners: every aeroplane this person owns at this organization, fleet or customer, primary first. **Owners, admins, dispatchers and technicians only**; anybody else is answered 404, because the list can name a customer's aeroplane.",
        "responses": {
          "200": {
            "description": "The aircraft, primary first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listOwnedAircraft",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/work-orders/{id}/files": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "List a job's files",
        "description": "Photos and documents on the job and on its items. `url` is a link that works for 15 minutes; `visibility` is `shop` (the shop's own) or `owner` (one the owner may be shown). **Owners, admins and technicians only**; anybody else is answered 403 for any id.",
        "responses": {
          "200": {
            "description": "The files, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrderFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a work-order role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Work order not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listWorkOrderFiles",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Attach files to a job",
        "description": "Send `fileNames` (up to 5 per request; jpg, png, heic or pdf), optionally `itemId` to put them on one item, `visibility` (`shop` by default) and a `label` for a single file. The answer has the new rows in `data` and, in the same order, `signedUrlData`: a presigned POST for each file, which the client posts the bytes to. A job holds up to 60 files. Refused in the demo. Files may be added after the job is invoiced.",
        "responses": {
          "201": {
            "description": "The rows and where to upload each file."
          },
          "400": {
            "description": "A file name, the item or the count is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed, or the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Work order not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrderFiles",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files to attach.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5
                  },
                  "itemId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "One of this job's items."
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                }
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}/files/{fileId}": {
      "patch": {
        "tags": [
          "Work orders"
        ],
        "summary": "Change a job's file",
        "description": "Its `label`, its `visibility`, or the item it is on (`itemId`, null for the job itself).",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderFile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateWorkOrderFile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The fields to change.",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  },
                  "itemId": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Work orders"
        ],
        "summary": "Remove a job's file",
        "description": "It stops listing; the stored object is kept.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteWorkOrderFile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ]
      }
    },
    "/resources/{id}/profile": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "Read an aircraft's profile",
        "description": "What the shop keeps about an aircraft beyond its registration: the airframe's total time (in tenths, as recorded and carried forward by the Hobbs flown since, `airframeTotalNowTenths`), the engine and propeller, and notes. Empty fields are null; an aircraft nobody has filled in answers all nulls. **Owners, admins, dispatchers and technicians only**; anybody else is answered 403 for any id.",
        "responses": {
          "200": {
            "description": "The profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a shop role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getAircraftProfile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Change an aircraft's profile",
        "description": "Send only what changes. `airframeTotalTenths` is the total time read now; send `airframeTotalAtHobbs` (tenths) with it when it was read at another Hobbs, otherwise the aircraft's current Hobbs is taken. Null clears a field. **Owners, admins and technicians only.**",
        "responses": {
          "200": {
            "description": "The profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateAircraftProfile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The fields to change.",
                "properties": {
                  "airframeTotalTenths": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Total time in tenths of an hour."
                  },
                  "airframeTotalAtHobbs": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "The Hobbs (tenths) it was read at."
                  },
                  "engineModel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "engineSerial": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "propModel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "propSerial": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        }
      }
    },
    "/resources/{id}/meters": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "Read an aircraft's meter log",
        "description": "Every recorded Hobbs and tach reading (`kind: reading`, with who, when, a note, a 15-minute photo link, the values it replaced, and `workOrder` when it was a job's reading in or out) beside the readings close-outs took (`kind: close-out`, with the booking), newest first, the last 100 of each. All hours are tenths. `meterMode` says which meters the aircraft has. `stale` is true for a customer's aircraft with meters not read in over 30 days. **Owners, admins, dispatchers and technicians only**; anybody else is answered 403 for any id.",
        "responses": {
          "200": {
            "description": "The meter log.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a shop role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getAircraftMeterLog",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ]
      },
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Record a meter reading",
        "description": "Moves the aircraft's own Hobbs and tach and runs the same hour check a close-out does, so hour-based inspections fire as after a flight. Send either meter or both, in tenths; a meter the aircraft does not have (see `meterMode`) is refused with 400. A reading lower than the aircraft's answers 409 `METER_LOWER` until sent again with `confirmLower: true` (a mistyped reading put right: kept as a correction, total time unchanged) or `meterReplaced: true` (a new meter: the airframe's total time carries on from it; refused with 400 on a reading that is not lower); one that passes an inspection that grounds the aircraft answers 409 `MAINTENANCE_TRIGGER` with `details.triggers` until sent with `confirmMaintenanceTrigger: true`. A reading dated before the newest one on record is kept in the log as history and does not change the aircraft (`appliedToAircraft: false`). Name a photo with `fileName` and upload it to the returned `signedUrlData`. **Owners, admins and technicians only.**",
        "responses": {
          "201": {
            "description": "The reading, with `signedUrlData` beside `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a reading.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed, or a photo in the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Lower than the aircraft, or would ground it: confirm and send again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "recordAircraftMeterReading",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The reading.",
                "properties": {
                  "hobbsTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Hobbs in tenths of an hour."
                  },
                  "tachTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Tach in tenths of an hour."
                  },
                  "readAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When it was read. Defaults to now; never in the future."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "fileName": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "A photo of the meter to upload (JPEG, PNG, HEIC or PDF)."
                  },
                  "confirmLower": {
                    "type": "boolean",
                    "description": "A lower reading is a mistyped one put right."
                  },
                  "meterReplaced": {
                    "type": "boolean",
                    "description": "A lower reading is a replaced meter: total time carries on from it."
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/owner/aircraft": {
      "get": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "List my aircraft",
        "description": "The aircraft the caller owns here, each with its open job and its most urgent open inspections (hours in tenths). Also any aircraft they do not own but have an open job or an unpaid bill on, with `owns: false`, their own jobs only and none of the aircraft's inspections or times. Empty for a member with neither. Found items waiting on an answer are counted in `needsAnswer`, and job by job, oldest first, in `needsAnswerJobs` (`jobId`, `label`, `count`); `needsAnswerJobId` names the first of them. A job that is closed or invoiced asks for no answers.",
        "responses": {
          "200": {
            "description": "My aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMyAircraft",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            },
            "description": "Aircraft to return, 1 to 1000. Defaults to all."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Aircraft to skip, for paging. Defaults to 0."
          }
        ]
      }
    },
    "/owner/aircraft/{id}": {
      "get": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Read one of my aircraft",
        "description": "Its jobs (newest first), every open inspection, its recent Hobbs and tach readings, `papers`, the aircraft's papers the shop shows its owners (each with a 15-minute `url`; a customer's aircraft only), and `components`, its life-limited components on it now with their life left (`id`, `name`, `position`, `partNumber`, `serialNumber`, `installedOn`, `timeSince`, `limitHours`, `limitMonths` and `life`, as `GET /resources/{id}/components` reads them, without the shop's notes; a customer's aircraft only). `mayRecordTimes` is true for a customer's aircraft with meters.",
        "responses": {
          "200": {
            "description": "The aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyAircraft",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ]
      }
    },
    "/owner/aircraft/{id}/requests": {
      "post": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Request work on my aircraft",
        "description": "Opens a Requested job on the shop's board, billed to the aircraft's billed owner, and tells the shop. Only `request` is needed. `wantedBy` (a real date, today or later, within two years) is added to the request in words; the shop sets the promised date. `preferredFrom` (and `preferredTo`, for a range), `grounded` and `location` are kept on the job as said, and shown to the shop as the owner's wish, not its plan. `hobbsTime` and `tachTime` (tenths; a customer's aircraft only, 403 otherwise) are recorded on the aircraft's meter log as `POST /owner/aircraft/{id}/meters` would, linked to the job; a reading lower than on record, or one that would ground the aircraft, answers 409 BEFORE anything is opened, until sent again with `confirmLower` / `confirmMaintenanceTrigger`. Name up to five photos or PDFs in `fileNames` and upload them to the returned `signedUrlData`, as when attaching to a job; refused in the public demo.",
        "responses": {
          "201": {
            "description": "The job's id, label and `aircraftTimes` (`updated`, `logged`, or null), with `signedUrlData` beside `data` for the files.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Times on one of the organization's own aircraft, or files in the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The times are lower than the aircraft's, or would ground it: confirm and send again. Nothing was opened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "requestWork",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The request.",
                "properties": {
                  "request": {
                    "type": "string",
                    "maxLength": 1900,
                    "description": "What you would like done."
                  },
                  "wantedBy": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "When you would like it back."
                  },
                  "preferredFrom": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "When it can come in: the day, or the first day of a range."
                  },
                  "preferredTo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "The last day it can come in. Needs `preferredFrom`, and is not before it."
                  },
                  "grounded": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "True: it cannot fly as it is. False: it can fly in. Null or absent: not said."
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "description": "Where the aircraft is now, such as the airport and hangar."
                  },
                  "hobbsTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Its Hobbs now, in tenths."
                  },
                  "tachTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Its tach now, in tenths."
                  },
                  "confirmLower": {
                    "type": "boolean"
                  },
                  "meterReplaced": {
                    "type": "boolean"
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean"
                  },
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5,
                    "description": "Photos or PDFs to upload with it."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/owner/aircraft/{id}/meters": {
      "post": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Record my aircraft's times",
        "description": "Exactly as `POST /resources/{id}/meters`, for the aircraft's owner, on a customer's aircraft only: the school's own aircraft are read at every close-out and answer 403.",
        "responses": {
          "201": {
            "description": "The reading.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a reading.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "One of the organization's own aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Lower than the aircraft, or would ground it: confirm and send again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "recordMyAircraftTimes",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The reading.",
                "properties": {
                  "hobbsTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "tachTime": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "readAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "fileName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "confirmLower": {
                    "type": "boolean"
                  },
                  "meterReplaced": {
                    "type": "boolean"
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/owner/aircraft/{id}/history": {
      "get": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "List what has been done to my aircraft",
        "description": "One of the caller's aircraft, newest first: its inspection sign-offs and the shop's completed jobs (no squawks, no technicians, no certificate numbers, nothing the shop keeps to itself). Paged, at most 250 a page. Filter with `kind` (`inspection`, `work_order`).",
        "responses": {
          "200": {
            "description": "A page of the history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AircraftHistoryEntry"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMyAircraftHistory",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "inspection"
            },
            "description": "Only these kinds, comma separated."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "date"
              ]
            },
            "description": "`date` to order by date."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "`desc` (default) or `asc`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250
            },
            "description": "Rows to return, 1 to 250."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          }
        ]
      }
    },
    "/owner/files": {
      "get": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "List the files on my account",
        "description": "What the shop shows you on your account, and the documents you added yourself (`yours`), newest first. Never a file the shop keeps to itself.",
        "responses": {
          "200": {
            "description": "Your files.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OwnFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMyFiles",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Add documents to my account",
        "description": "Name up to 5 files and upload each to the returned `signedUrlData`. The shop always sees them, and so do you. You can keep up to 20 of your own files. Turned off in the public demo.",
        "responses": {
          "201": {
            "description": "The files, with `signedUrlData` beside `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OwnFile"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No files, or too many.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The public demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addMyFiles",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                },
                "required": [
                  "fileNames"
                ]
              }
            }
          }
        }
      }
    },
    "/owner/files/{fileId}": {
      "delete": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Remove a document I added",
        "description": "Only a file you added yourself; any other id answers 404, the same as one that does not exist.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not a file you added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteMyFile",
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ]
      }
    },
    "/owner/work-orders/{id}": {
      "get": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Read a job on my aircraft",
        "description": "The owner's view of a job: where it stands, the items (and which found ones wait on an answer), the files shared with the owner, and, only for the person billed, the charges and the invoice (`refunded` when handed back in full, `pastDue` when unpaid past its `dueAt`). A finding the shop has not sent is not shown, nor anything priced against it. `lines` and `chargesCents` leave out the lines of findings the owner has not agreed to (still waiting, declined or deferred); once the job is invoiced they are the bill's, every billable line, so `chargesCents` is the invoice's subtotal, and no item `needsAnswer`. Each item carries `estimateCents`, what its billable lines come to (null for anybody but the person billed), `answeredByYou`, true when the caller gave its answer here, and `askedAt`, when the shop sent it (null if never), which an answer sends back. `mayAnswer` is true while the job is open and not invoiced; `ownsAircraft` says whether the caller owns the aircraft. No shop notes, costs, markups or rates.",
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not on your aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      }
    },
    "/owner/work-orders/{id}/answers": {
      "post": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Answer the shop's found items",
        "description": "Approve, decline or defer found items still waiting on an answer, on an open job. Any owner of the aircraft answers, or the person the job is billed to; the answer names who gave it. An item already answered or done is refused: call the shop to change an answer. Each answer sends back the item's `askedAt` from the job: a finding reworded since (or reworded and sent again) is refused with a 409, to be read again first. Refused with a 409 once the job is invoiced.",
        "responses": {
          "201": {
            "description": "The answer as recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not an answer, no `askedAt`, or already answered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not on your aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Somebody has just answered it, the finding changed since it was read (reload it), or the job is invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "answerMyJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The answers.",
                "properties": {
                  "decisions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "itemId": {
                          "type": "integer"
                        },
                        "decision": {
                          "type": "string",
                          "enum": [
                            "approved",
                            "declined",
                            "deferred"
                          ]
                        },
                        "askedAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "The item's `askedAt` as the job showed it: when the shop sent it."
                        }
                      },
                      "required": [
                        "itemId",
                        "decision",
                        "askedAt"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/owner/work-orders/{id}/files": {
      "post": {
        "tags": [
          "Aircraft owners"
        ],
        "summary": "Attach photos to a job on my aircraft",
        "description": "Name up to 5 files and upload each to the returned `signedUrlData`. They are shown to the shop and to you. An owner adds up to 20 files to a job.",
        "responses": {
          "201": {
            "description": "The files, with `signedUrlData`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No files, or too many.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not on your aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "attachToMyJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/resources/{id}/components": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List an aircraft's life-limited components",
        "description": "Each component on the aircraft with `life`: its time since new or overhaul now (from the aircraft's meter on the clock it counts on, tach unless said), the life left on each limit, and `status` (amber `dueSoon` inside 10% of a limit or 10 hours, or 10% of a calendar limit or 30 days; `overdue` at or past it). Components that came off only with `removed=true`. All hours are TENTHS. Works on the organization's own aircraft and customer aircraft alike. **Owners, admins, dispatchers and technicians only**; anybody else is answered 403 for any id. Another organization's aircraft answers exactly as an absent one.",
        "responses": {
          "200": {
            "description": "The components.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AircraftComponent"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a shop role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAircraftComponents",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "removed",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "`true` to include components that came off."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Add a life-limited component",
        "description": "Only `name` is required. Not given, it went on today (`installedOn`) at the aircraft's reading now (`installedAtMeter`, on `meter`: the tach, or the Hobbs on a Hobbs-only aircraft), new (`timeSince`) with no time (`timeAtInstall: 0`). A limit (`limitHours` in tenths, `limitMonths`, or both, whichever comes first) is tracked by a linked one-off inspection per clock, due where the limit is reached, so it warns, reminds the owners of a customer aircraft and, with `groundAtLimit` (default true), grounds the aircraft like any inspection. An aircraft with no meters takes a calendar limit only. **Owners, admins and technicians only.**",
        "responses": {
          "201": {
            "description": "The component.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AircraftComponent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createAircraftComponent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The component.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60,
                    "example": "Left magneto"
                  },
                  "position": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "example": "Engine"
                  },
                  "partNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "serialNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "installedOn": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD, not in the future."
                  },
                  "installedAtMeter": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The aircraft's reading on `meter` when it went on, in tenths."
                  },
                  "meter": {
                    "type": "string",
                    "enum": [
                      "tach",
                      "hobbs"
                    ]
                  },
                  "timeAtInstall": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Its own time when it went on, in tenths."
                  },
                  "timeSince": {
                    "type": "string",
                    "enum": [
                      "new",
                      "overhaul"
                    ]
                  },
                  "sinceOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "When it was new or last overhauled; the calendar limit counts from here. Not after `installedOn`. Null means `installedOn`."
                  },
                  "limitHours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Tenths of an hour of its own time."
                  },
                  "limitMonths": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 600
                  },
                  "groundAtLimit": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}/components/{componentId}": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "Get a life-limited component",
        "description": "One component, as the list reads it. **Owners, admins, dispatchers and technicians only.**",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AircraftComponent"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a shop role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Component not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getAircraftComponent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "componentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The component id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Change a component, or record that it came off",
        "description": "Send only what changes; null clears. A changed limit or time MOVES its linked inspections' due point (the same inspections, with the notices the new point has not earned sent again when it is reached; one signed off gets a new open cycle), and releases the aircraft if that was all that held it. `removedOn` (with `removedAtMeter`, else the meter now) records that it came off: its time stops there and its inspections are retired, so nothing stays due. `removedOn: null` puts it back on. **Owners, admins and technicians only.**",
        "responses": {
          "200": {
            "description": "The component.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AircraftComponent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Component not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateAircraftComponent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "componentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The component id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The fields to change: any of those `POST` takes, and `removedOn` (YYYY-MM-DD) and `removedAtMeter` (tenths).",
                "properties": {
                  "removedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "removedAtMeter": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Delete a component entered by mistake",
        "description": "It stops listing and its inspections are retired (signed-off cycles are kept). For a part that came off, record `removedOn` instead. **Owners, admins and technicians only.**",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Component not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteAircraftComponent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "componentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The component id."
          }
        ]
      }
    },
    "/resources/{id}/history": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List an aircraft's maintenance history",
        "description": "Everything done to the aircraft, newest first: inspection sign-offs (including one-time inspections no longer on the active list), resolved squawks, and completed work orders, each with the date, what it was, who signed it off or did it, and the Hobbs and tach at the time in tenths where known. Paged in the database, at most 250 rows a page; `pagination.limit` is the limit applied. Filter with `kind` (comma separated: `inspection`, `squawk`, `work_order`); `sort=date` with `order=asc` reverses it. **Owners, admins, dispatchers and technicians only**, answered 403 for any id otherwise; work orders only for owners, admins and technicians.",
        "responses": {
          "200": {
            "description": "A page of the history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AircraftHistoryEntry"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a shop role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAircraftHistory",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "inspection,work_order"
            },
            "description": "Only these kinds, comma separated."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "date"
              ]
            },
            "description": "`date` to order by date."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "`desc` (default) or `asc`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 250
            },
            "description": "Rows to return, 1 to 250."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          }
        ]
      }
    },
    "/resources/{id}/invoices": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List an aircraft's invoices",
        "description": "Every bill tied to the aircraft: for a booking that flew it (`flight`, `prepaid_package`), and for a shop job on it (`work_order`), newest first unless sorted. `sort` is a dot path into the row (`id`, `createdAt`, `totalCents`, `number`, `status`, `billedTo.name`). **Admins only**, like every organization-wide invoice list.",
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AircraftInvoice"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Aircraft not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAircraftInvoices",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/files": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List the files on a customer's account",
        "description": "Signed authorizations, insurance, correspondence: every file on the account, newest first, each kept to the shop (`shop`) or shown to the customer (`owner`), and those the customer added themselves (`fromCustomer`). **Owners, admins and technicians only**, and never about yourself; anybody else is answered 403 for any id.",
        "responses": {
          "200": {
            "description": "The files.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCustomerFiles",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Add files to a customer's account",
        "description": "Name up to 5 files (jpg, png, heic or pdf) and upload each to the returned `signedUrlData`. `visibility` is `shop` (default) or `owner`; `label` names a single file. An account holds up to 60 files. Turned off in the public demo. **Owners, admins and technicians only.**",
        "responses": {
          "201": {
            "description": "The files, with `signedUrlData` beside `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerFile"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No files, too many, or not a photo or PDF.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed, or the public demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addCustomerFiles",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                },
                "required": [
                  "fileNames"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/files/{fileId}": {
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Rename a customer's file, or show or hide it",
        "description": "Send `label` and/or `visibility` (`shop` or `owner`). A file the customer added themselves stays visible to them: making it `shop` is refused with 400. **Owners, admins and technicians only.**",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CustomerFile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That file is not on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateCustomerFile",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What to change.",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Members"
        ],
        "summary": "Remove a file from a customer's account",
        "description": "It stops listing. **Owners, admins and technicians only.**",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That file is not on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteCustomerFile",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ]
      }
    },
    "/resources/{id}/meters/{readingId}/photo": {
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Remove a reading's photo",
        "description": "For a photo that never finished uploading: the reading stops pointing at it. **Owners, admins and technicians only.**",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Reading not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "clearMeterReadingPhoto",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "readingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reading id."
          }
        ]
      }
    },
    "/orgUsers/{orgUserId}/customer": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Read what the shop keeps about a customer",
        "description": "Billing address, preferred contact (`email`, `phone`, `text` or null) and internal notes. Never part of the member's own record. **Owners, admins and technicians only**; anybody else is answered 403 for any id.",
        "responses": {
          "200": {
            "description": "The customer profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCustomerProfile",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Change what the shop keeps about a customer",
        "description": "Send only what changes; null clears. **Owners and admins only.**",
        "responses": {
          "200": {
            "description": "The customer profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateCustomerProfile",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The fields to change.",
                "properties": {
                  "billingAddress": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "preferredContact": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "email",
                      "phone",
                      "text",
                      null
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/owner-sign-in-email": {
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Email an aircraft owner how to sign in",
        "description": "Emails an aircraft owner the organization recorded, who has not signed up, at the address on their record: sign in or create an account with that address, then join with the organization's code. Signing up with that address and confirming it hands them the organization's record of them. Stores nothing (it is not an invitation). Worded from what they have here: their aircraft, or for somebody billed who owns none, their jobs and bills. Refused for somebody who has signed up, who is not an owner the organization recorded, who is archived, who owns no aircraft here and has no open job or unpaid bill (signing in would show them nothing), who has no address on record, or who turned off these emails. In the public demo it answers `sent: false` and emails nobody. Limited to 10 an hour per organization and 2 an hour per owner. **Admins only.**",
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "boolean",
                          "description": "False only in the public demo, where no email leaves."
                        },
                        "to": {
                          "type": "string",
                          "format": "email",
                          "description": "The address it went to."
                        },
                        "code": {
                          "type": "string",
                          "description": "The organization's join code, as the email gives it."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not an owner who can be emailed this; the message says why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Sent too many in the last hour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "emailOwnerSignIn",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The owner's membership id."
          }
        ]
      }
    },
    "/resources/{id}/files": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List papers on an aircraft",
        "description": "POH, weight and balance, and other files on the tail. Any org member may read `bookers` files. Staff, dispatchers, and technicians also see `staff` files. Soft-deleted rows are omitted.",
        "responses": {
          "200": {
            "description": "The papers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResourceFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listResourceFiles",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Add papers to an aircraft",
        "description": "Send `fileNames` (jpg, jpeg, png, heic, heif, or pdf, up to five in that category). The server writes the S3 keys and returns `signedUrlData` for the client to POST each file to. `fileUrls` in the body is refused. Requires owner, admin, dispatcher, or technician. Uploading is turned off in the public demo.",
        "responses": {
          "201": {
            "description": "The papers. `signedUrlData` is a list of S3 presigned POSTs in the same order as the created rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResourceFile"
                      }
                    },
                    "signedUrlData": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PresignedPost"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed to upload, or this is the public demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createResourceFiles",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The papers.",
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "airworthiness_certificate",
                      "registration",
                      "poh",
                      "operating_limitations",
                      "weight_and_balance",
                      "insurance",
                      "form_337",
                      "logbook_scan",
                      "other"
                    ]
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "bookers",
                      "staff"
                    ],
                    "description": "Defaults: POH and weight and balance are bookers; the rest are staff."
                  },
                  "ownerVisible": {
                    "type": "boolean",
                    "description": "Show it to the aircraft's owners in their portal. A customer's aircraft only; refused with 400 on the organization's own."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5,
                    "description": "Original file names. The server mints the S3 keys."
                  }
                },
                "required": [
                  "category",
                  "fileNames"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}/files/{fileId}": {
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Rename or recategorize a paper",
        "description": "Changes label, visibility, or category. Does not replace the bytes. Requires owner, admin, dispatcher, or technician.",
        "responses": {
          "200": {
            "description": "The paper.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ResourceFile"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That file is not on this aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateResourceFile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The paper id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What to change.",
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "airworthiness_certificate",
                      "registration",
                      "poh",
                      "operating_limitations",
                      "weight_and_balance",
                      "insurance",
                      "form_337",
                      "logbook_scan",
                      "other"
                    ]
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "bookers",
                      "staff"
                    ]
                  },
                  "ownerVisible": {
                    "type": "boolean",
                    "description": "Show it to the aircraft's owners, or stop. A customer's aircraft only."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Remove a paper from an aircraft",
        "description": "Soft-deletes the row and drops the S3 object. A stale W&B should come down. Requires owner, admin, dispatcher, or technician.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That file is not on this aircraft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteResourceFile",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The paper id."
          }
        ]
      }
    },
    "/audit": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "List audit events",
        "description": "The organization's activity, newest first. Administrators only , this is every member's movements in one list.\n\nPaged with `limit` and `offset` like every other collection here; `pagination.total` tells you how many match your filters.",
        "responses": {
          "200": {
            "description": "The audit events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unreadable filter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAuditEvents",
        "parameters": [
          {
            "name": "entityType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "reservation",
                "invoice",
                "squawk",
                "resource",
                "orgUser",
                "joinRequest"
              ]
            },
            "description": "Only events about this kind of thing."
          },
          {
            "name": "actorOrgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only events by this member."
          },
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only events touching this aircraft, simulator or room."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/audit/{entityType}/{entityId}": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "Get one thing's history",
        "description": "Everything recorded about a single entity, oldest first , what the reservation and invoice drawers render. Guarded by the same rule that guards the entity itself, so this is never more permissive than the record it describes.\n\nHistory begins when auditing was switched on; anything older cannot be reconstructed.",
        "responses": {
          "200": {
            "description": "The events, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditEvent"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not an entity we keep a trail for.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You are not allowed to see this entity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getEntityAuditTrail",
        "parameters": [
          {
            "name": "entityType",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "One of `reservation`, `invoice`, `squawk`, `resource`, `orgUser`, `joinRequest`, `workOrder` (owners, admins and technicians only)."
          },
          {
            "name": "entityId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The id of that entity."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/maintenance/squawks": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List squawks",
        "description": "Reported discrepancies. Visible to technicians, dispatchers, and administrators.",
        "responses": {
          "200": {
            "description": "The squawks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Squawk"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listSquawks",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "resolved",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on resolved state."
          },
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated resource ids."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Report a squawk",
        "description": "Note the resource is given as a flat `resourceId` here, where reservations take a nested `resource: { id }`. That inconsistency is real and is on the list to reconcile. Photos and PDFs are attached by sending `fileNames` (jpg, jpeg, png, heic, heif, or pdf, up to five). The server writes the S3 keys and returns `signedUrlData` for the client to POST each file to. `fileUrls` in the body is refused. Uploading is turned off in the public demo; a text squawk still files.",
        "responses": {
          "201": {
            "description": "The squawk. When files were attached, `signedUrlData` is a list of S3 presigned POSTs in the same order as `fileNames`. Upload each file after this response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Squawk"
                    },
                    "signedUrlData": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PresignedPost"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createSquawk",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The discrepancy.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "resourceId": {
                    "type": "integer"
                  },
                  "priority": {
                    "type": "string",
                    "enum": [
                      "grounding",
                      "high",
                      "normal",
                      "low"
                    ],
                    "description": "How urgent it is. Defaults to `normal`. `grounding` records that the aircraft should not fly, and is what the Squawks report's Grounding filter selects; it does not by itself set the aircraft's `grounded` flag."
                  },
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5,
                    "description": "Original file names. jpg, jpeg, png, heic, heif, or pdf. The server mints the S3 keys."
                  }
                },
                "required": [
                  "title",
                  "description",
                  "resourceId"
                ]
              }
            }
          }
        }
      }
    },
    "/maintenance/squawks/{squawkId}": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Get a squawk",
        "description": "One reported discrepancy.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Squawk"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such squawk.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getSquawk",
        "parameters": [
          {
            "name": "squawkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The squawk id."
          }
        ]
      },
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Act on a squawk",
        "description": "Resolves or reopens a squawk. This is a POST rather than a PATCH for historical reasons; an `action` field in the body is required.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Squawk"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such squawk, or no `action` was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateSquawk",
        "parameters": [
          {
            "name": "squawkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The squawk id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What to do.",
                "properties": {
                  "action": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "action"
                ]
              }
            }
          }
        }
      }
    },
    "/maintenance/squawks/{squawkId}/comments": {
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Add a note to a squawk",
        "description": "Appends one note to the squawk's thread, for recording progress on a defect without closing it. Technicians and administrators only, matching who may verify or resolve one. Notes are append-only: there is no edit and no delete, and a mistake is corrected by adding another note. A photo or PDF may stand in for text: `body` may be empty when `fileNames` is present. Read them back on the squawk itself, which returns `comments` oldest first.",
        "responses": {
          "201": {
            "description": "The note. When files were attached, `signedUrlData` is a list of S3 presigned POSTs in the same order as `fileNames`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SquawkComment"
                    },
                    "signedUrlData": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PresignedPost"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The note was empty, longer than 2000 characters, or the files were refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a technician or administrator, or the squawk belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addSquawkComment",
        "parameters": [
          {
            "name": "squawkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The squawk id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The note. Send `body`, `fileNames`, or both. Empty with neither is refused.",
                "properties": {
                  "body": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5,
                    "description": "Original file names. jpg, jpeg, png, heic, heif, or pdf. The server mints the S3 keys."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/maintenance/resources/{resourceId}/squawks": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List an aircraft's squawks",
        "description": "Discrepancies against one resource.",
        "responses": {
          "200": {
            "description": "The squawks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Squawk"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listSquawksForResource",
        "parameters": [
          {
            "name": "resourceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          },
          {
            "name": "resolved",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on resolved state."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/maintenance/reminders": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List maintenance reminders",
        "description": "Live reminders across the fleet, most urgent first.\n\nEach reminder carries a computed `due` block , how many hours are left on the meter, or how many days until the date , so you don't have to recombine the template's interval with the aircraft's meters yourself.",
        "responses": {
          "200": {
            "description": "The reminders.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MaintenanceReminder"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReminders",
        "parameters": [
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only reminders on these aircraft. Repeat or comma-separate for several."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter on the computed band: `overdue`, `dueSoon`, `ok`, `resolved` or `notApplicable`. Repeat or comma-separate for several."
          },
          {
            "name": "resolved",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on signed-off state."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text over the reminder, its template and the tail number."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/maintenance/reminders/presets": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List inspection presets",
        "description": "The AVIATES airworthiness inspections , annual, VOR, 100-hour, AD review, transponder, ELT and static/altimeter , plus the shop intervals that come up as often, and the registration renewal (84 calendar months from the issue date, 14 CFR 47.40, warned 180, 60 and 30 days out to the shop and the owners, never grounding by default).\n\nReference data, not records: nothing exists until you POST a preset's `payload` to `/maintenance/reminders/templates` like any other template. The intervals are the common case and are editable afterwards; `caveat` says where a default doesn't apply to every aircraft.",
        "responses": {
          "200": {
            "description": "The presets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InspectionPreset"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listInspectionPresets",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/maintenance/reminders/{maintenanceReminderId}/files": {
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Attach files to an open inspection",
        "description": "Photos or PDFs on the OPEN reminder (packing slip, in-progress photos). Admin or technician. Copied onto the compliance record at sign-off, then cleared. Send `fileNames` only.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addReminderFiles",
        "parameters": [
          {
            "name": "maintenanceReminderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files to attach.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Display names with jpg, png, heic, or pdf extensions. Up to five on the inspection."
                  }
                },
                "required": [
                  "fileNames"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Remove a file from an open inspection",
        "description": "Removes one working file by its file name. Does not touch a signed compliance record.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminder"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "removeReminderFile",
        "parameters": [
          {
            "name": "maintenanceReminderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which file.",
                "properties": {
                  "fileName": {
                    "type": "string",
                    "description": "The file name as shown, e.g. packing-slip.pdf."
                  }
                },
                "required": [
                  "fileName"
                ]
              }
            }
          }
        }
      }
    },
    "/maintenance/reminders/{maintenanceReminderId}": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Get a maintenance reminder",
        "description": "One reminder.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminder"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such reminder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getReminder",
        "parameters": [
          {
            "name": "maintenanceReminderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Set this aircraft's reminder steps, or mark it not applicable",
        "description": "**Technicians and admins.** Send either or both.\n\n`reminderSteps`: this inspection's reminder steps on this one aircraft, when they should differ from the rule's: the same shape as the rule's `reminderSteps`, each key present here winning over the rule's. Null follows the rule again. Carried to the next cycle when it is signed off. Steps further out than one already sent this cycle are not sent after it. The reminder's `steps` is what applies; `ownSteps` is what this aircraft sets itself.\n\n`notApplicable`: true marks the inspection as not applying to this aircraft (a VOR check on a VFR-only aircraft), with an optional `notApplicableReason`. While marked it is never warned, reminded, counted overdue or grounded, and an aircraft grounded for it alone goes back on the line. False puts it back: it is checked at once, so one already overdue grounds the aircraft and notifies straight away. Carried to the next cycle when it is signed off.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither field sent, a field not valid, or the inspection is signed off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a technician or admin here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateReminderSteps",
        "parameters": [
          {
            "name": "maintenanceReminderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The steps, whether it applies, or both.",
                "properties": {
                  "reminderSteps": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "description": "When it is said, and to whom (Murray spec 6): `shopDays` and `ownerDays`, days before a calendar due date; `shopHours` and `ownerHours`, TENTHS of an hour before an hour one. Each a list of up to 6 whole numbers (days 1 to 365; tenths 1 to 10000). An absent key is the default: the shop's single `remindDaysBefore` / `remindHoursBefore`, the owners' 30 and 7 days and 10 hours. An empty list is none before it is due. Due always goes out, and the shop hears once more a day after it is overdue. Setting the shop's lists also sets `remindDaysBefore` / `remindHoursBefore` to their furthest step. Null puts every list back on its default.",
                    "properties": {
                      "shopDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "shopHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "notApplicable": {
                    "type": "boolean",
                    "description": "True: this inspection does not apply to this aircraft. False: it applies again."
                  },
                  "notApplicableReason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "Why it does not apply, kept with the mark. Ignored when `notApplicable` is false.",
                    "example": "VFR only, no VOR receiver"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Sign off a maintenance reminder",
        "description": "Marks the work done, which starts the next interval if the template repeats. POST rather than PATCH for historical reasons.\n\nSend `methodOfCompliance` and `mechanicName` to also write a permanent compliance record, which is what 14 CFR 91.417 asks for on an Airworthiness Directive. That record is append-only and cannot be edited or removed afterwards, and it snapshots the rule's number and revision as they stood at signature. Omit them and the reminder simply rolls forward as before, keeping no history.\n\nPhotos and PDFs already on the open inspection (`POST /maintenance/reminders/{id}/files`) are copied onto that compliance record, then cleared so they do not follow the next cycle. Do not send `fileUrls`.\n\n`tachAtCompliance` and `hobbsAtCompliance` higher than the aircraft's own (and lower on neither) are also recorded on the aircraft's meter log and move its meters; the response's `aircraftTimes` says so (`updated`, or `logged` for a reading older than its newest). If those readings pass another inspection that grounds the aircraft, the sign-off answers 409 `MAINTENANCE_TRIGGER` before anything is signed; send again with `confirmMaintenanceTrigger: true`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The readings at sign-off pass an inspection that grounds the aircraft: `code` is `MAINTENANCE_TRIGGER`. Nothing was signed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "completeReminder",
        "parameters": [
          {
            "name": "maintenanceReminderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Completion detail. The compliance fields are optional together: supplying `methodOfCompliance` requires `mechanicName`.",
                "properties": {
                  "completedAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the work was done."
                  },
                  "completedHours": {
                    "type": "integer",
                    "description": "Meter reading the next interval counts from, in tenths of an hour."
                  },
                  "notes": {
                    "type": "string",
                    "description": "Free-text note on the reminder itself."
                  },
                  "methodOfCompliance": {
                    "type": "string",
                    "description": "What was actually done. Writing this creates the permanent record."
                  },
                  "mechanicName": {
                    "type": "string",
                    "description": "Who certified the work. Required alongside `methodOfCompliance`."
                  },
                  "mechanicCertificateNumber": {
                    "type": "string",
                    "description": "Their FAA certificate number."
                  },
                  "mechanicCertificateType": {
                    "type": "string",
                    "description": "A&P, IA, or repair station."
                  },
                  "tachAtCompliance": {
                    "type": "integer",
                    "description": "Tach reading at compliance, NOT time in service as 14 CFR 1.1 defines it, in tenths. Defaults to the aircraft's current reading."
                  },
                  "hobbsAtCompliance": {
                    "type": "integer",
                    "description": "Hobbs reading at compliance, NOT time in service as 14 CFR 1.1 defines it, in tenths. Defaults to the aircraft's current reading."
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean",
                    "description": "Accept that the readings at sign-off ground the aircraft (see the 409)."
                  }
                },
                "required": [
                  "completedAt"
                ]
              }
            }
          }
        }
      }
    },
    "/users/mechanicCertificate": {
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Save your mechanic certificate",
        "description": "Stores the caller's own FAA certificate on their membership at the active organization, so signing an inspection off can prefill it rather than asking for six exact characters every time.\n\nScoped to the caller: there is no id in the path. Send an empty string to clear one entered wrongly. The certificate on a compliance record is snapshotted at signature and is not changed by editing this.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateMechanicCertificate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The certificate.",
                "properties": {
                  "mechanicCertificateNumber": {
                    "type": "string",
                    "description": "The number as printed.",
                    "example": "3421887"
                  },
                  "mechanicCertificateType": {
                    "type": "string",
                    "description": "A&P, IA, or repair station.",
                    "example": "IA"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/maintenance/compliance": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List compliance records",
        "description": "Every inspection this organization has signed off, newest first, kept for the life of the aircraft.\n\nThis is the airworthiness history an inspector asks for. Readable by any member for the same reason squawks are: it is information about an aircraft somebody is about to fly.",
        "responses": {
          "200": {
            "description": "The compliance records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MaintenanceComplianceRecord"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listComplianceRecords",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text over the rule name, document number, mechanic and method."
          },
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Restrict to one or more aircraft. Comma-separated."
          },
          {
            "name": "reminderId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Restrict to one inspection's history."
          },
          {
            "name": "sourceType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Restrict to a kind of rule, for example `ad`."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only records complied with on or after this date."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only records complied with on or before this date."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/maintenance/compliance/{complianceRecordId}": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Get a compliance record",
        "description": "One signed-off inspection, in full.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceComplianceRecord"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getComplianceRecord",
        "parameters": [
          {
            "name": "complianceRecordId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The compliance record id."
          }
        ]
      }
    },
    "/invoices": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Create an invoice",
        "description": "Bills a member for something that isn't a flight , a checkout fee, a headset, a late cancellation. Line items are validated against the organization before anything is written.\n\nFor billing a completed flight, use `POST /reservations/{id}/invoices` instead: it prices the booking from the meters you recorded.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "The invoice, with Stripe's tax on each line and `total` = `subtotal` + `tax`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A line item or the customer failed validation, a line is taxable but the organization has no rate, or a taxed charge would go on a member's account balance (ledger mode), which cannot hold sales tax.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`expectedTotal` no longer matches: the bill changed since it was priced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createInvoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The invoice. Exactly one of `customer` or `guest` is required.",
                "properties": {
                  "customer": {
                    "type": "object",
                    "description": "Bill a member. Mutually exclusive with `guest`.",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "Organization user id."
                      }
                    },
                    "required": [
                      "id"
                    ]
                  },
                  "guest": {
                    "type": "object",
                    "description": "Bill a non-member by name and email. Mutually exclusive with `customer`.",
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Guest display name."
                      },
                      "email": {
                        "type": "string",
                        "format": "email",
                        "description": "Where Stripe sends the pay link."
                      }
                    },
                    "required": [
                      "name",
                      "email"
                    ]
                  },
                  "memo": {
                    "type": "string",
                    "description": "Optional note shown on the invoice."
                  },
                  "items": {
                    "type": "array",
                    "description": "Line items. `unitPrice` is in cents, before tax.\n\n**Sales tax** follows the organization's own setup (see `GET /organizations/salesTax`): a line in a category the organization taxes is taxed at that category's rate, `taxable` overrides that for one line, and a customer marked exempt is never taxed. A line that sends neither `category` nor `taxable` is never taxed, so clients written before sales tax existed keep raising the bills they show.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "qty": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 99,
                          "description": "A whole number."
                        },
                        "unitPrice": {
                          "type": "integer",
                          "description": "Unit price in cents."
                        },
                        "category": {
                          "type": "string",
                          "enum": [
                            "labor",
                            "part",
                            "supply",
                            "outside_service",
                            "freight",
                            "fee",
                            "rental",
                            "instruction",
                            "other"
                          ],
                          "description": "What the line is. Decides whether it is taxed by default, and where it is filed in QuickBooks."
                        },
                        "taxable": {
                          "type": "boolean",
                          "description": "Tax this line (or not), whatever the organization's rule for its category says."
                        }
                      },
                      "required": [
                        "name",
                        "qty",
                        "unitPrice"
                      ]
                    }
                  },
                  "dueAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When payment is due."
                  },
                  "dueIn": {
                    "type": "integer",
                    "description": "Days until due, used when Stripe needs a relative due."
                  },
                  "serviceFeeTaxable": {
                    "type": "boolean",
                    "description": "Tax the organization's service fee line (or not), whatever the organization's rule for Fees says. Absent follows the rule."
                  },
                  "expectedTotal": {
                    "type": "integer",
                    "description": "Optional. The `total` from `POST /invoices/preview`. When it no longer matches (a tax rate, rule or exemption changed in between), nothing is raised and the answer is 409 with the new total."
                  }
                },
                "required": [
                  "items"
                ]
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List invoices",
        "description": "Every invoice in the organization. **Administrators only** , this is the whole membership's payment history plus revenue aggregates, so it is treated as a financial surface rather than an operational one. Dispatchers get the per-reservation invoice instead.",
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The date range is unparseable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listInvoices",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "paid",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on paid state."
          },
          {
            "name": "voided",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on voided state. `paid=false` on its own still includes voided invoices, which nobody owes; pass `voided=false` for outstanding."
          },
          {
            "name": "overdue",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Only invoices that are past due: unpaid, not voided, and carrying a `dueAt` in the past. An invoice with no due date is never overdue. Matches the `Overdue` status of the revenue report."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/invoices/preview": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Price an invoice without raising it",
        "description": "Takes exactly the body of `POST /invoices` and answers what that call would charge: each line with its sales tax, the organization's service fee line, tax per rate, and the total. Nothing is written and Stripe is not called. The same permission as creating the invoice.\n\nSend the `total` back as `expectedTotal` on the create to be sure nothing moved in between.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InvoicePreview"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body would fail `POST /invoices` for the same reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "previewInvoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The same body as `POST /invoices`."
              }
            }
          }
        }
      }
    },
    "/organizations/salesTax": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "summary": "Get sales tax settings",
        "description": "The organization's sales tax rates, archived ones included, and which categories of invoice line each rate taxes. **Administrators.** Nothing is taxed until a rate exists and a category points at it (or a person ticks a line). Close-out bills for flights are not taxed.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SalesTaxSettings"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getSalesTaxSettings"
      }
    },
    "/work-orders": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "List work orders",
        "description": "**Owners, admins and technicians only** (not dispatchers), on any aircraft, because a job carries the owner and money; everybody else gets 403 for any request. Defaults to the open jobs, oldest first (the job board); `state=closed` lists completed and cancelled jobs newest first.",
        "responses": {
          "200": {
            "description": "The jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrder"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listWorkOrders",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed",
                "all"
              ]
            },
            "description": "`open` (default), `closed`, or `all`."
          },
          {
            "name": "resourceId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated aircraft ids: those aircraft's jobs."
          },
          {
            "name": "billToOrgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated member ids: the jobs billed to any of them."
          },
          {
            "name": "ownerOrgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated member ids: jobs on aircraft any of them owns, billed to them or not."
          },
          {
            "name": "technicianOrgUserId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated member ids: jobs with any of them assigned as a technician."
          },
          {
            "name": "locationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated location ids: jobs on aircraft based at any of them."
          },
          {
            "name": "reservationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated booking ids: the jobs a maintenance booking holds."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Matches the job number (1042, WO-1042), the tail number, the person billed, or the request."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Open a work order",
        "description": "**Owners, admins and technicians only.** Opens a job on an aircraft and gives it the organization's next number (from WO-1001). The person billed defaults to the aircraft's primary owner; send `billToOrgUserId: null` for the organization's own aircraft. Every id in the body must belong to this organization.",
        "responses": {
          "201": {
            "description": "The new job.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field failed validation, or an id is not in this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The readings in need confirming first: `code` is `METER_LOWER` or `MAINTENANCE_TRIGGER`. Nothing was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The job.",
                "properties": {
                  "resourceId": {
                    "type": "integer",
                    "description": "The aircraft."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "requested",
                      "scheduled",
                      "received",
                      "in_progress",
                      "waiting_owner",
                      "waiting_parts",
                      "ready"
                    ],
                    "description": "The stage a new job starts in; it starts open (never completed or cancelled). Defaults to `requested`. An in-the-shop stage stamps `receivedAt`."
                  },
                  "complaint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000,
                    "description": "What the owner asked for, in their words."
                  },
                  "receivedAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "ISO 8601 with an offset, e.g. 2026-10-14T09:30:00Z. Refused in the future, on a job that is `requested` or `scheduled`, and as null while the aircraft is at the shop."
                  },
                  "promisedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "The day the shop told the owner it would be ready."
                  },
                  "completedAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "ISO 8601 with an offset. Only a `completed` job has one, it cannot be cleared while the job is completed, and it is refused in the future or before `receivedAt`."
                  },
                  "hobbsIn": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Hobbs at arrival, in TENTHS of an hour (1234.5 hours is 12345). A changed reading in or out is also recorded on the aircraft's meter log (`GET /resources/{id}/meters`), linked to the job, and moves the aircraft's own meters as recording it there would; one dated before the aircraft's newest reading is kept as history only. See `confirmLower`."
                  },
                  "tachIn": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Tach at arrival, in tenths."
                  },
                  "hobbsOut": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Hobbs at return to service, in tenths. Recorded on the aircraft's meter log as its return to service, like `hobbsIn`."
                  },
                  "tachOut": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Tach at return to service, in tenths."
                  },
                  "customerNotes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Notes the owner may be shown."
                  },
                  "internalNotes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "The shop's own notes."
                  },
                  "holdOwnerNotices": {
                    "type": "boolean",
                    "description": "Open the job without telling the owners: no booked-in, arrived or ready notices until the hold is lifted. Default false."
                  },
                  "billToOrgUserId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Who is billed: a member of this organization, usually the aircraft's owner. Null on the organization's own aircraft. Cannot change while an invoice is live. Chosen by an admin; a technician gets the aircraft's owner (403 otherwise)."
                  },
                  "reservationId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "A maintenance booking on the same aircraft that holds the hangar slot."
                  },
                  "technicianOrgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "maxItems": 20,
                    "description": "People with the technician role assigned. Replaces the whole list; somebody already on the job who lost the role may stay."
                  },
                  "confirmLower": {
                    "type": "boolean",
                    "description": "A reading in or out lower than the aircraft's own answers 409 `METER_LOWER` before anything saves; send again with this to record it as a correction."
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean",
                    "description": "A reading that passes an inspection that grounds the aircraft answers 409 `MAINTENANCE_TRIGGER` (with `details.triggers`) before anything saves; send again with this to save it and ground the aircraft."
                  }
                },
                "required": [
                  "resourceId"
                ]
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "Get a work order",
        "description": "**Owners, admins and technicians only.** 404 for an id that is not one of this organization's jobs.",
        "responses": {
          "200": {
            "description": "The job.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getWorkOrder",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Work orders"
        ],
        "summary": "Change a work order",
        "description": "**Owners, admins and technicians only.** Only the fields sent move. Everything a person changes is recorded in the audit trail, with the job's number, aircraft and the person billed.",
        "responses": {
          "200": {
            "description": "The job as it now stands.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field failed validation, an id is not in this organization, or the change is refused while an invoice is live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The readings need confirming first: `code` is `METER_LOWER` or `MAINTENANCE_TRIGGER`. Nothing was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateWorkOrder",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "requested",
                      "scheduled",
                      "received",
                      "in_progress",
                      "waiting_owner",
                      "waiting_parts",
                      "ready",
                      "completed",
                      "cancelled"
                    ],
                    "description": "The stage the shop sets. Moving to an in-the-shop stage stamps `receivedAt`, and moving back to `requested` or `scheduled` clears it; `completed` stamps `completedAt`; reopening clears it. A job with a live invoice cannot be cancelled."
                  },
                  "complaint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000,
                    "description": "What the owner asked for, in their words."
                  },
                  "receivedAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "ISO 8601 with an offset, e.g. 2026-10-14T09:30:00Z. Refused in the future, on a job that is `requested` or `scheduled`, and as null while the aircraft is at the shop."
                  },
                  "promisedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "The day the shop told the owner it would be ready."
                  },
                  "completedAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "ISO 8601 with an offset. Only a `completed` job has one, it cannot be cleared while the job is completed, and it is refused in the future or before `receivedAt`."
                  },
                  "hobbsIn": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Hobbs at arrival, in TENTHS of an hour (1234.5 hours is 12345). A changed reading in or out is also recorded on the aircraft's meter log (`GET /resources/{id}/meters`), linked to the job, and moves the aircraft's own meters as recording it there would; one dated before the aircraft's newest reading is kept as history only. See `confirmLower`."
                  },
                  "tachIn": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Tach at arrival, in tenths."
                  },
                  "hobbsOut": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Hobbs at return to service, in tenths. Recorded on the aircraft's meter log as its return to service, like `hobbsIn`."
                  },
                  "tachOut": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999,
                    "description": "Tach at return to service, in tenths."
                  },
                  "customerNotes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Notes the owner may be shown."
                  },
                  "internalNotes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "The shop's own notes."
                  },
                  "holdOwnerNotices": {
                    "type": "boolean",
                    "description": "Hold or release the owners' stage notices. Releasing it tells the owners where the job stands now (booked in, arrived or ready); sending findings also releases it."
                  },
                  "billToOrgUserId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Who is billed: a member of this organization, usually the aircraft's owner. Null on the organization's own aircraft. Cannot change while an invoice is live. Chosen by an admin; a technician gets the aircraft's owner (403 otherwise)."
                  },
                  "reservationId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "A maintenance booking on the same aircraft that holds the hangar slot."
                  },
                  "technicianOrgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "maxItems": 20,
                    "description": "People with the technician role assigned. Replaces the whole list; somebody already on the job who lost the role may stay."
                  },
                  "confirmLower": {
                    "type": "boolean",
                    "description": "A reading in or out lower than the aircraft's own answers 409 `METER_LOWER` before anything saves; send again with this to record it as a correction."
                  },
                  "confirmMaintenanceTrigger": {
                    "type": "boolean",
                    "description": "A reading that passes an inspection that grounds the aircraft answers 409 `MAINTENANCE_TRIGGER` (with `details.triggers`) before anything saves; send again with this to save it and ground the aircraft."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Work orders"
        ],
        "summary": "Delete a work order",
        "description": "**Admins only.** For a job opened by mistake. A job that has ever had an invoice, voided or not, stays on the record: cancel it instead (`status: cancelled`). Its notifications are removed with it, the shop's and the owners'. The audit trail keeps a line saying it existed.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The job has been invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteWorkOrder",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      }
    },
    "/work-orders/{id}/items": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "List a job's items",
        "description": "**Owners, admins and technicians only.** In order. 404 for a job that is not this organization's.",
        "responses": {
          "200": {
            "description": "The items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrderItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listWorkOrderItems",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Add an item to a job",
        "description": "**Owners, admins and technicians only.** Something the owner asked for (`requested`) or the shop found (`found`). Attach an open inspection or squawk on the same aircraft and the item is done when that is signed off or resolved.",
        "responses": {
          "201": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrderItem",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The item.",
                "properties": {
                  "source": {
                    "type": "string",
                    "enum": [
                      "requested",
                      "found"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "maintenanceReminderId": {
                    "type": "integer",
                    "description": "An open inspection on the job's aircraft."
                  },
                  "squawkId": {
                    "type": "integer",
                    "description": "An open squawk on the job's aircraft."
                  }
                },
                "required": [
                  "source",
                  "description"
                ]
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}/items/{itemId}": {
      "patch": {
        "tags": [
          "Work orders"
        ],
        "summary": "Change an item",
        "description": "**Owners, admins and technicians only.** Reword it, or mark a plain item done (`done: true`) or not. An item tied to an inspection or squawk is refused: sign off the inspection or resolve the squawk. Rewording a found item the owner was sent or answered (and not done) clears its answer and its send: it must be asked again. On a job that is invoiced or closed it cannot be asked again, so the wording changes and the answer stands. Ticking, rewording or removing a finding that was waiting on the owner may move a job at Waiting on owner back to In progress; unticking one that was sent and not answered moves a job at Received or In progress to Waiting on owner.",
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not this job's item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateWorkOrderItem",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The item id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset.",
                "properties": {
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "done": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Work orders"
        ],
        "summary": "Remove an item",
        "description": "**Owners, admins and technicians only.** Lines charged against it stay on the job. A finding on a customer's aircraft never sent to the owner, answered or done, that still has charges on it, is refused: take its lines off first, or send it to the owner.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "A finding the owner was never told about that still has charges.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not this job's item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteWorkOrderItem",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The item id."
          }
        ]
      }
    },
    "/work-orders/settings": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "Get the shop's rates",
        "description": "**Owners, admins and technicians only.** The labor rate and default markups that price new lines.",
        "responses": {
          "200": {
            "description": "The rates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderSettings"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getWorkOrderSettings"
      },
      "put": {
        "tags": [
          "Work orders"
        ],
        "summary": "Change the shop's rates",
        "description": "**Admins only.** Any subset. Lines already on jobs keep their price.",
        "responses": {
          "200": {
            "description": "The rates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderSettings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateWorkOrderSettings",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset.",
                "properties": {
                  "laborRateCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000
                  },
                  "partsMarkupBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "outsideWorkMarkupBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000
                  }
                }
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}/lines": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "List a job's lines",
        "description": "**Owners, admins and technicians only.** In order.",
        "responses": {
          "200": {
            "description": "The lines.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrderLine"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listWorkOrderLines",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Add a line",
        "description": "**Owners, admins and technicians only.** Labor is priced from `minutes` at `rateCents` (the shop rate by default); a part or outside work from `costCents` plus the markup (the organization default by default) unless `unitPriceCents` is sent. A line can be at most $999,999.99. Refused once the job has a live invoice.\n\n**Only an admin prices.** Anyone else in the shop sends `rateCents`, `markupBps`, `billable` and `taxable` only with the shop's defaults, and `unitPriceCents` only for supplies, freight, fees and other (403 otherwise).",
        "responses": {
          "201": {
            "description": "The line.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderLine"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation, or the job is invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrderLine",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The line.",
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "labor",
                      "part",
                      "supply",
                      "outside_service",
                      "freight",
                      "fee",
                      "other"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "qty": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9999,
                    "description": "Not for labor (always 1)."
                  },
                  "costCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Parts and outside work: what the shop paid per unit. Priced with the markup when no price is sent."
                  },
                  "markupBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Overrides the organization's default markup (parts and outside work). Admin only."
                  },
                  "unitPriceCents": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "The customer's price per unit. For labor, a flat charge instead of hours at the rate."
                  },
                  "discountBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000,
                    "description": "A discount on the line in basis points (1000 is 10% off), applied per unit before tax and said on the invoice line. Admin only."
                  },
                  "taxable": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Decide the tax on this line; null follows the organization's rule."
                  },
                  "billable": {
                    "type": "boolean"
                  },
                  "minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100000,
                    "description": "Labor: clock minutes."
                  },
                  "rateCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Labor: overrides the shop rate. Admin only."
                  },
                  "workedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "technicianOrgUserId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Labor: who did the work, a person with the technician role."
                  },
                  "partNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "serialNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "partStatus": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "ordered",
                      "received",
                      "installed",
                      "returned",
                      "unused",
                      null
                    ]
                  },
                  "orderedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "expectedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "itemId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "An item on the same job."
                  }
                },
                "required": [
                  "category",
                  "description"
                ]
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}/lines/{lineId}": {
      "patch": {
        "tags": [
          "Work orders"
        ],
        "summary": "Change a line",
        "description": "**Owners, admins and technicians only.** Any subset; the line is re-priced only when its hours, rate, cost or markup CHANGE and no price is sent, so resending them keeps a price set by hand. Refused once the job has a live invoice. Somebody other than an admin changes only the lines they entered, and only as on create.",
        "responses": {
          "200": {
            "description": "The line.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrderLine"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation, or the job is invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not this job's line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateWorkOrderLine",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The line id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset.",
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "labor",
                      "part",
                      "supply",
                      "outside_service",
                      "freight",
                      "fee",
                      "other"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "qty": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 9999,
                    "description": "Not for labor (always 1)."
                  },
                  "costCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Parts and outside work: what the shop paid per unit. Priced with the markup when no price is sent."
                  },
                  "markupBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Overrides the organization's default markup (parts and outside work). Admin only."
                  },
                  "unitPriceCents": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "The customer's price per unit. For labor, a flat charge instead of hours at the rate."
                  },
                  "discountBps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000,
                    "description": "A discount on the line in basis points (1000 is 10% off), applied per unit before tax and said on the invoice line. Admin only."
                  },
                  "taxable": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Decide the tax on this line; null follows the organization's rule."
                  },
                  "billable": {
                    "type": "boolean"
                  },
                  "minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100000,
                    "description": "Labor: clock minutes."
                  },
                  "rateCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Labor: overrides the shop rate. Admin only."
                  },
                  "workedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "technicianOrgUserId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Labor: who did the work, a person with the technician role."
                  },
                  "partNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "serialNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "partStatus": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "ordered",
                      "received",
                      "installed",
                      "returned",
                      "unused",
                      null
                    ]
                  },
                  "orderedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "expectedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "itemId": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "An item on the same job."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Work orders"
        ],
        "summary": "Remove a line",
        "description": "**Owners, admins and technicians only.** Refused once the job has a live invoice. Somebody other than an admin removes only the lines they entered.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The job is invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not this job's line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteWorkOrderLine",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The line id."
          }
        ]
      }
    },
    "/work-orders/{id}/invoice/preview": {
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Price a job's invoice",
        "description": "**Admins only.** The bill as it would be raised now: the job's billable lines, each with its sales tax, the organization's service fee, and the total. Priced by the same code as the invoice. Lines on an item the owner declined or deferred are left off (`leftOff`).",
        "responses": {
          "200": {
            "description": "The priced bill.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/InvoicePreview"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "details": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "description": "What the invoice also says beyond its lines (Murray's §13): up to four custom fields printed at its top (the work order, the aircraft, Hobbs and tach in and out) and a description: the job, the completed work and the notes for the owner, fitted within Stripe's limit with room left for a tax exemption.",
                              "properties": {
                                "customFields": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "name": {
                                        "type": "string"
                                      },
                                      "value": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "name",
                                      "value"
                                    ]
                                  }
                                },
                                "description": {
                                  "type": "string"
                                },
                                "shortened": {
                                  "type": "boolean",
                                  "description": "Something was left out to fit: the list ends \"and N more\", or the notes were cut."
                                },
                                "hash": {
                                  "type": "string",
                                  "description": "Identifies this text. Send it back as `expectedDetails` when raising the invoice."
                                }
                              }
                            },
                            "ownerNotTold": {
                              "type": "array",
                              "description": "Findings with charges on this bill that were never sent to the owner, answered or done: the owner has not heard of them. Raising is refused while there are any, unless `includeUnsent` is sent.",
                              "items": {
                                "$ref": "#/components/schemas/WorkOrderUnagreedCharge"
                              }
                            },
                            "awaitingAnswer": {
                              "type": "array",
                              "description": "Findings with charges on this bill that were sent to the owner and are still waiting on their answer. Once invoiced the owner can no longer answer them. Raising is refused while there are any, unless `includeUnanswered` is sent.",
                              "items": {
                                "$ref": "#/components/schemas/WorkOrderUnagreedCharge"
                              }
                            },
                            "leftOff": {
                              "type": "array",
                              "description": "Items the owner declined or deferred, found by the shop or asked for by the owner, whose lines are left off this bill. To bill one, record a new answer for it, or move its lines off the item.",
                              "items": {
                                "allOf": [
                                  {
                                    "$ref": "#/components/schemas/WorkOrderUnagreedCharge"
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "decision": {
                                        "type": "string",
                                        "enum": [
                                          "declined",
                                          "deferred"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "decision"
                                    ]
                                  }
                                ]
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Nobody billed, nothing billable (or everything with charges was declined or deferred), the job was cancelled, or a line is marked taxable with no sales tax rate to charge it at (`code`: `TAX_RATE_MISSING`; add one under Settings, Billing, Sales tax).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "previewWorkOrderInvoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      }
    },
    "/work-orders/{id}/invoice": {
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Raise a job's invoice",
        "description": "**Admins only.** Raises the job's invoice to the person billed, as a work order bill linked to the job, through Stripe in both billing modes (never onto an account balance), with sales tax by the organization's rules. One live invoice per job: void it to raise another. Send `expectedTotal` and `expectedDetails` from the preview; a 409 carries the current total if either moved. Refused in the public demo.",
        "responses": {
          "201": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Nobody billed, nothing billable, an owner with no address, or the job already has an invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not an admin, or the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "With `data.total`: the total (or, with `data.details`, the invoice's text) moved since it was priced, and that is the current total. Without it: findings with charges were never sent to the owner (send `includeUnsent` to bill them anyway) or are still waiting on the owner's answer (send `includeUnanswered`), or the job changed while the invoice was being made, and that invoice was voided (or Stripe would not void it: void it in Billing).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrderInvoice",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional.",
                "properties": {
                  "expectedTotal": {
                    "type": "integer",
                    "description": "The total the preview showed, in cents. Anything but a whole number is refused."
                  },
                  "expectedDetails": {
                    "type": "string",
                    "description": "The preview's `details.hash`: refused with a 409 (`data.details` true) if the notes for the owner or the finished work changed since."
                  },
                  "dueIn": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 365,
                    "description": "Days until due."
                  },
                  "includeUnsent": {
                    "type": "boolean",
                    "description": "Bill the findings in the preview's `ownerNotTold` anyway. Without it, a bill charging for a finding never sent to the owner is refused with a 409."
                  },
                  "includeUnanswered": {
                    "type": "boolean",
                    "description": "Bill the findings in the preview's `awaitingAnswer` anyway. Without it, a bill charging for a finding still waiting on the owner's answer is refused with a 409."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/work-orders/{id}/send-to-owner": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "Who a send would reach",
        "description": "**Owners, admins and technicians only.** Who Send to owner would reach on this job right now, by the same rules as the send, sending nothing: the aircraft's owners and the person billed, less the caller. `blocked` says why a send would reach nobody (no owner on record, nobody with an address, or the caller is the only owner), and is null when it would reach somebody.",
        "responses": {
          "200": {
            "description": "Who it would reach.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "tail": {
                          "type": "string",
                          "description": "The aircraft's tail number."
                        },
                        "reached": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Who a send would reach."
                        },
                        "notReached": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "reason": {
                                "type": "string"
                              }
                            }
                          },
                          "description": "Who it would not, and why."
                        },
                        "you": {
                          "type": "boolean",
                          "description": "The caller owns the aircraft or is the person billed: nobody is sent their own findings."
                        },
                        "blocked": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Why a send would reach nobody; null when it would reach somebody."
                        }
                      },
                      "required": [
                        "tail",
                        "reached",
                        "notReached",
                        "you",
                        "blocked"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getWorkOrderSendAudience",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Send findings to the owners",
        "description": "**Owners, admins and technicians only.** Sends the found items still waiting on an answer that have not been sent yet to every owner of the aircraft and the person billed, in one message (prices only to the person billed). An owner who has not signed up is emailed at the address on record, with a way to sign up. Until a finding is sent the owners are not shown it. A job in Received or In progress moves to Waiting on owner, and back to In progress once every finding sent is answered. Answers 409 when there is nothing new to send, or when nobody can be reached, with the reason (nothing is marked sent then). `GET` on the same path says who would be reached before sending.",
        "responses": {
          "201": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "integer",
                          "description": "Findings sent."
                        },
                        "reached": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Who it reached."
                        },
                        "notReached": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "reason": {
                                "type": "string"
                              }
                            }
                          },
                          "description": "Who it did not, and why."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nothing new to send, or nobody could be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "sendWorkOrderFindingsToOwner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ]
      }
    },
    "/work-orders/{id}/approvals": {
      "get": {
        "tags": [
          "Work orders"
        ],
        "summary": "List the owner's answers",
        "description": "**Owners, admins and technicians only.** The phone calls recorded on a job, newest first.",
        "responses": {
          "200": {
            "description": "The calls.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrderApproval"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listWorkOrderApprovals",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Work orders"
        ],
        "summary": "Record the owner's answer",
        "description": "**Owners, admins and technicians only.** A record of a phone call, not an owner approval flow: who was reached, when (defaults to the moment it is recorded), the spend limit, notes, and approved, declined or deferred for each item discussed. Every item must be on this job. An item a LATER call (or the owner, in AerScheduler) already answered keeps that answer: the call still records what was said, and the item is listed in `kept`. A job at Waiting on owner goes back to In progress when this answers the last finding sent that was waiting. Refused with a 409 while the job has a live invoice: void it to change the work.",
        "responses": {
          "201": {
            "description": "The call, and what took its answer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "The call's id."
                        },
                        "applied": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          },
                          "description": "The items that took this call's answer."
                        },
                        "kept": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "itemId": {
                                "type": "integer"
                              },
                              "description": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "itemId",
                              "description"
                            ]
                          },
                          "description": "The items a later answer was kept on."
                        }
                      },
                      "required": [
                        "id",
                        "applied",
                        "kept"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation, or an item is not on this job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not one of this organization's jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The job is invoiced: void the invoice to change the work.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createWorkOrderApproval",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The work order id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The call.",
                "properties": {
                  "contactName": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "contactedAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601 with an offset. Refused in the future."
                  },
                  "spendLimitCents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 99999999
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "decisions": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "itemId": {
                          "type": "integer"
                        },
                        "decision": {
                          "type": "string",
                          "enum": [
                            "approved",
                            "declined",
                            "deferred"
                          ]
                        }
                      },
                      "required": [
                        "itemId",
                        "decision"
                      ]
                    }
                  }
                },
                "required": [
                  "contactName",
                  "decisions"
                ]
              }
            }
          }
        }
      }
    },
    "/organizations/salesTax/rates": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Add a sales tax rate",
        "description": "**Owners and admins.** Adding a rate taxes nothing by itself: point a category at it with `PUT /organizations/salesTax/rules`.",
        "responses": {
          "201": {
            "description": "The new rate's id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Name or percentage failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only the organization owner can change sales tax.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createSalesTaxRate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rate.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 50,
                    "description": "Printed on invoices, e.g. `Idaho sales tax`."
                  },
                  "ratePpm": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 300000,
                    "description": "Parts per million of the amount taxed: 60000 is 6%, 81875 is 8.1875%."
                  },
                  "country": {
                    "type": "string",
                    "description": "Where the tax is owed, as an ISO 3166-1 alpha-2 code. Defaults to `US`."
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "Optional. In the US, a state (code or name, stored as the code); anything else is refused. Abroad, a region as you want it printed, up to 40 characters, never read as a US state."
                  }
                },
                "required": [
                  "name",
                  "ratePpm"
                ]
              }
            }
          }
        }
      }
    },
    "/organizations/salesTax/rates/{rateId}": {
      "patch": {
        "tags": [
          "Organizations"
        ],
        "summary": "Change a sales tax rate",
        "description": "**Owners and admins.** Rename freely. The percentage, country and state change only while no invoice line has been charged at the rate; after that, archive it and add the new rate, so old invoices keep saying what they charged and where it is owed.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Failed validation, or the rate is already on invoices and only its name can change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only the organization owner can change sales tax.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such rate at this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateSalesTaxRate",
        "parameters": [
          {
            "name": "rateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The rate id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any subset.",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "ratePpm": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 300000
                  },
                  "country": {
                    "type": "string"
                  },
                  "jurisdiction": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/organizations/salesTax/rates/{rateId}/archive": {
      "post": {
        "tags": [
          "Organizations"
        ],
        "summary": "Archive a sales tax rate",
        "description": "**Owners and admins.** Stops the rate applying to new invoices, and removes it from every category that used it, so those categories stop being taxed. Invoices already raised keep the rate they were charged.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only the organization owner can change sales tax.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such rate at this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "archiveSalesTaxRate",
        "parameters": [
          {
            "name": "rateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The rate id."
          }
        ]
      }
    },
    "/organizations/salesTax/rules": {
      "put": {
        "tags": [
          "Organizations"
        ],
        "summary": "Set which lines are taxed",
        "description": "**Owners and admins.** Replaces the whole table in one save: `{ rules: { part: 3, supply: null } }` taxes parts at rate 3 and nothing else. Categories: `labor`, `part`, `supply`, `outside_service`, `freight`, `fee` (the organization's service fee line is a fee), `rental`, `instruction`, `other`.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "rules": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unknown category, or a rate that does not exist here or is archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only the organization owner can change sales tax.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setSalesTaxRules",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rules.",
                "properties": {
                  "rules": {
                    "type": "object",
                    "additionalProperties": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                },
                "required": [
                  "rules"
                ]
              }
            }
          }
        }
      }
    },
    "/orgUsers/{orgUserId}/taxExemption": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a customer's sales tax exemption",
        "description": "**Administrators.** Why this customer pays no sales tax and the organization's note, or `{ reason: null }` when they are taxed normally.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such person at this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getTaxExemption",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member or customer."
          }
        ]
      },
      "put": {
        "tags": [
          "Members"
        ],
        "summary": "Mark a customer exempt from sales tax",
        "description": "**Administrators.** Sets why this customer pays no sales tax (`resale`, `government`, `carrier`, `nonresident`, `agricultural`, `nonprofit`, `tribal` or `other`) and a note naming the certificate the organization holds, or clears it with `{ \"reason\": null }`. From then on, bills raised by hand to them carry no tax and say why.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown reason, or a note over 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such person at this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setTaxExemption",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member or customer."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The exemption.",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "resale",
                      "government",
                      "carrier",
                      "nonresident",
                      "agricultural",
                      "nonprofit",
                      "tribal",
                      "other",
                      null
                    ]
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  }
                },
                "required": [
                  "reason"
                ]
              }
            }
          }
        }
      }
    },
    "/invoices/summary": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Summarise invoices",
        "description": "Revenue and outstanding totals for a window, in cents, with the counts behind them. **Administrators only**, like the list.\n\nTakes the same `startDate`, `endDate` and `q` as `GET /invoices`, so it describes exactly the invoices that endpoint would return , except `paid`, which narrows the list but not this. Use it instead of adding up a list you fetched: lists are capped at 1,000 rows, so summing what you received would give you the total of an arbitrary thousand invoices for any organization with more than that.",
        "responses": {
          "200": {
            "description": "The totals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "revenue": {
                          "type": "integer",
                          "description": "Sum of paid invoice totals BEFORE sales tax (total minus tax), in cents. Sales tax is collected for the state, not earned."
                        },
                        "salesTaxCollected": {
                          "type": "integer",
                          "description": "Sales tax on those paid invoices, in cents."
                        },
                        "paidCount": {
                          "type": "integer",
                          "description": "How many were paid."
                        },
                        "outstanding": {
                          "type": "integer",
                          "description": "Sum of totals that are neither paid nor void, in cents. Void invoices are owed by nobody and are excluded."
                        },
                        "outstandingCount": {
                          "type": "integer",
                          "description": "How many are outstanding."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The date range is unparseable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "summariseInvoices",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "voided",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on voided state. `outstanding` already excludes voided invoices; this narrows `revenue` too."
          }
        ]
      }
    },
    "/invoices/{invoiceId}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Get an invoice",
        "description": "One invoice with its line items. An aircraft owner from outside the organization reads only their own bills, and gets what they need to read and pay one: its number, dates, status, memo, lines with their tax, totals, how a desk payment came in (`paymentMethod`, `checkNumber`, `paymentReceivedOn`), the payment and PDF links, and the work order it is for. Not the organization's QuickBooks sync state, its note about the payment, or which of its staff raised, voided or marked it paid. `billTo` (name, billing address, phone, email) is on the bill for the person billed and for administrators.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getInvoice",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Invoices"
        ],
        "summary": "Mark an invoice paid, or void it",
        "description": "Send `markPaid: true` to record a payment taken outside the card flow, optionally saying how: `paymentMethod`, `checkNumber` (only with `check`), `paymentReceivedOn` (the day it arrived, `YYYY-MM-DD`; today in the organization's calendar when left out, never in the future) and `paymentNote`. Stripe is told the invoice was paid outside it, and the person billed is sent a receipt. Or send `markVoided: true`. Payment fields without `markPaid` are refused. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Already voided, a field is not valid, or payment fields without markPaid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateInvoice",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "What to do.",
                "properties": {
                  "markPaid": {
                    "type": "boolean",
                    "description": "Record it paid."
                  },
                  "markVoided": {
                    "type": "boolean",
                    "description": "Void it."
                  },
                  "paymentMethod": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "cash",
                      "check",
                      "card_in_person",
                      "bank_transfer",
                      "other",
                      null
                    ]
                  },
                  "checkNumber": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Only with `paymentMethod: check`."
                  },
                  "paymentReceivedOn": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "The day the money arrived. Not in the future."
                  },
                  "paymentNote": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoiceId}/files": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List an invoice's files",
        "description": "The vendor's invoice, a receipt, an 8130-3 attached to the bill. `url` works for 15 minutes. Administrators read every file; the person billed reads only the ones shown to them (`visibility: owner`). Anybody else is answered 403, as for an id that is not this organization's.",
        "responses": {
          "200": {
            "description": "The files, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InvoiceFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not yours, or not this organization's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listInvoiceFiles",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Attach files to an invoice",
        "description": "Send `fileNames` (up to 5 per request; jpg, png, heic or pdf), `visibility` (`shop` by default, `owner` to show them to the person billed) and a `label` for a single file. The answer has the new rows in `data` and, in the same order, `signedUrlData`: a presigned POST for each file, which the client posts the bytes to. An invoice holds up to 30 files. Requires the **admin** role. Refused in the demo.",
        "responses": {
          "201": {
            "description": "The rows and where to upload each file."
          },
          "400": {
            "description": "A file name or the count is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed, not this organization's, or the demo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createInvoiceFiles",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The files to attach.",
                "properties": {
                  "fileNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 5
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoiceId}/files/from-job": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Add a job's files to its invoice",
        "description": "Copies the work order's files onto the invoice raised for it: every one not already there, or those named in `fileIds`. Each keeps its label and who it is shown to. Nothing is uploaded. Requires the **admin** role. Refused in the demo.",
        "responses": {
          "201": {
            "description": "The files added; empty when every one was already there.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/InvoiceFile"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a work order's invoice, or a file is not on its job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed, or not this organization's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "addInvoiceFilesFromJob",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which files, or none for all.",
                "properties": {
                  "fileIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoiceId}/files/{fileId}": {
      "patch": {
        "tags": [
          "Invoices"
        ],
        "summary": "Change an invoice's file",
        "description": "Its `label`, or who it is shown to (`visibility`). Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/InvoiceFile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateInvoiceFile",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The fields to change.",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "shop",
                      "owner"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Invoices"
        ],
        "summary": "Remove an invoice's file",
        "description": "It stops listing; the stored object is kept. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "File not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteInvoiceFile",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The file id."
          }
        ]
      }
    },
    "/invoices/{invoiceId}/remind": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Send a payment reminder",
        "description": "Emails the member about an outstanding invoice. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "Already paid, or reminded too recently.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "remindInvoice",
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The invoice id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/invoices/orgUsers/{orgUserId}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List a member's invoices",
        "description": "Your own, or anyone's if you are an administrator.",
        "responses": {
          "200": {
            "description": "The invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not your invoices, and you are not an administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listInvoicesForMember",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          },
          {
            "name": "paid",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on paid state."
          },
          {
            "name": "voided",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Filter on voided state. `paid=false` on its own still includes voided invoices, which nobody owes; pass `voided=false` for outstanding."
          },
          {
            "name": "overdue",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Only invoices that are past due: unpaid, not voided, and carrying a `dueAt` in the past. An invoice with no due date is never overdue. Matches the `Overdue` status of the revenue report."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/invoices/reservation/{reservationId}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Get a reservation's invoice",
        "description": "The bill for one flight. Dispatchers can read this even though they cannot list the organization's invoices.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That flight has not been invoiced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getInvoiceForReservation",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ]
      }
    },
    "/invoices/reservation/{reservationId}/invoiceTotals": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Get a reservation's totals",
        "description": "What the flight comes to, broken down, without creating an invoice.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such reservation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getInvoiceTotalsForReservation",
        "parameters": [
          {
            "name": "reservationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reservation id."
          }
        ]
      }
    },
    "/announcements": {
      "get": {
        "tags": [
          "Announcements"
        ],
        "summary": "List announcements",
        "description": "Notices posted to your organization, newest first.",
        "responses": {
          "200": {
            "description": "The announcements.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Announcement"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listAnnouncements",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "expired",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "`true` for notices whose `expireAt` has passed, `false` for those still live. Omit for both. A notice with no `expireAt` never expires and counts as live."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Announcements"
        ],
        "summary": "Post an announcement",
        "description": "Notifies the roles you target. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Announcement"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Announcement"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createAnnouncement",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The notice.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "expireAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "forRoles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "title",
                  "message"
                ]
              }
            }
          }
        }
      }
    },
    "/announcements/{id}": {
      "patch": {
        "tags": [
          "Announcements"
        ],
        "summary": "Update an announcement",
        "description": "Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Announcement"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateAnnouncement",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The announcement id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "expireAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Announcements"
        ],
        "summary": "Delete an announcement",
        "description": "Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteAnnouncement",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The announcement id."
          }
        ]
      }
    },
    "/announcements/{id}/seen": {
      "post": {
        "tags": [
          "Announcements"
        ],
        "summary": "Mark an announcement as seen",
        "description": "Hides the notice from your Home. It stays on the announcements page. Idempotent.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Announcement"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That announcement is not in your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "markAnnouncementSeen",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The announcement id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/notifications": {
      "get": {
        "tags": [
          "Notifications"
        ],
        "summary": "List your notifications",
        "description": "Your own in-app notifications.",
        "responses": {
          "200": {
            "description": "The notifications.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listNotifications",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "unread",
                "read",
                "all"
              ]
            },
            "description": "Filter by read state. Defaults to `all`."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Notifications"
        ],
        "summary": "Clear all your notifications",
        "description": "Removes every notification for the signed-in member. A POST rather than a DELETE for historical reasons.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "clearNotifications",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Notifications"
        ],
        "summary": "Register a push device token",
        "description": "Despite the path, this does not update notifications , it registers a device for push. A separate concern that shares the route for historical reasons.",
        "responses": {
          "200": {
            "description": "Registered."
          },
          "400": {
            "description": "No token supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "registerDeviceToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The device token.",
                "properties": {
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        }
      }
    },
    "/notifications/{notificationId}": {
      "post": {
        "tags": [
          "Notifications"
        ],
        "summary": "Mark a notification read",
        "description": "A POST rather than a PATCH for historical reasons.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "That notification is not yours.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "markNotificationRead",
        "parameters": [
          {
            "name": "notificationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The notification id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/standby": {
      "post": {
        "tags": [
          "Standby"
        ],
        "summary": "Create standby interest",
        "description": "Creates standby interest for yourself. Owners, admins, and dispatchers may create it for another member.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StandbyInterest"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The matching criteria are invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createStandbyInterest",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The matching criteria.",
                "properties": {
                  "orgUserId": {
                    "type": "integer"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "on_reservation",
                      "open_window",
                      "standing"
                    ]
                  },
                  "watchedReservationId": {
                    "type": "integer"
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "criteria": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "reservationType": {
                    "type": "string"
                  },
                  "resourceId": {
                    "type": "integer"
                  },
                  "instructorOrgUserId": {
                    "type": "integer"
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10
                  }
                },
                "required": [
                  "kind"
                ]
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Standby"
        ],
        "summary": "List organization standby interest",
        "description": "Owners, admins, and dispatchers can list standby interest, optionally for one watched reservation.",
        "responses": {
          "200": {
            "description": "The standby interest records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StandbyInterest"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listStandbyInterest",
        "parameters": [
          {
            "name": "reservationId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only interest watching this reservation."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/standby/me": {
      "get": {
        "tags": [
          "Standby"
        ],
        "summary": "List my standby interest",
        "description": "Lists standby interest owned by the signed-in member.",
        "responses": {
          "200": {
            "description": "Your standby interest records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StandbyInterest"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMyStandbyInterest",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/standby/suggestions": {
      "get": {
        "tags": [
          "Standby"
        ],
        "summary": "Get my slot suggestion setting",
        "description": "Whether AerScheduler may offer you a cancelled slot that fits how you usually fly, even when you did not stand by for it.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "enabled"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getSlotSuggestions"
      },
      "put": {
        "tags": [
          "Standby"
        ],
        "summary": "Turn slot suggestions on or off",
        "description": "Turns suggested slot offers on or off for the signed-in member. Offers from your own standby sign-ups are unaffected.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "enabled"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "setSlotSuggestions",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new setting.",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              }
            }
          }
        }
      }
    },
    "/standby/{id}": {
      "delete": {
        "tags": [
          "Standby"
        ],
        "summary": "Withdraw standby interest",
        "description": "Withdraws your own active interest. Desk staff may withdraw any interest in their organization.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StandbyInterest"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The interest is no longer active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "withdrawStandbyInterest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The standby interest id."
          }
        ]
      }
    },
    "/slot-offers": {
      "get": {
        "tags": [
          "Standby"
        ],
        "summary": "List pending slot offers",
        "description": "Owners, admins, and dispatchers list every pending offer in their organization. An instructor lists only the offers holding their own time, without the offered member's email.",
        "responses": {
          "200": {
            "description": "The pending offers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SlotOffer"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listPendingSlotOffers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Standby"
        ],
        "summary": "Create a slot offer",
        "description": "Desk staff can offer a slot directly or start recovery from a cancelled reservation.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SlotOffer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The offer is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createSlotOffer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The offered window, or a sourceReservationId for recovery.",
                "properties": {
                  "sourceReservationId": {
                    "type": "integer"
                  },
                  "offeredToOrgUserId": {
                    "type": "integer"
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "reservationType": {
                    "type": "string"
                  },
                  "timeZoneName": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "resourceId": {
                    "type": "integer"
                  },
                  "instructorOrgUserId": {
                    "type": "integer"
                  },
                  "locationId": {
                    "type": "integer"
                  },
                  "standbyInterestId": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/slot-offers/me": {
      "get": {
        "tags": [
          "Standby"
        ],
        "summary": "List my slot offers",
        "description": "Lists every slot offer sent to the signed-in member.",
        "responses": {
          "200": {
            "description": "Your slot offers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SlotOffer"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMySlotOffers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/slot-offers/{id}/accept": {
      "post": {
        "tags": [
          "Standby"
        ],
        "summary": "Accept a slot offer",
        "description": "Claims a pending offer and creates the corresponding reservation.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SlotOffer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The offer expired or was already answered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "acceptSlotOffer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The slot offer id."
          }
        ]
      }
    },
    "/slot-offers/{id}/decline": {
      "post": {
        "tags": [
          "Standby"
        ],
        "summary": "Decline a slot offer",
        "description": "Declines your pending offer and rolls recovery to the next eligible member.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SlotOffer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The offer is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "declineSlotOffer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The slot offer id."
          }
        ]
      }
    },
    "/slot-offers/{id}/withdraw": {
      "post": {
        "tags": [
          "Standby"
        ],
        "summary": "Withdraw a slot offer",
        "description": "Frees the soft hold a pending offer keeps on the aircraft and instructor, and stops the chain. Desk staff may withdraw any offer; an instructor may withdraw an offer holding their own time. Decline and expiry still roll to the next eligible member.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SlotOffer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The offer is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "withdrawSlotOffer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The slot offer id."
          }
        ]
      }
    },
    "/booking-requests/policy/me": {
      "get": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Check if I need approval",
        "description": "Returns whether the signed-in member must submit booking requests instead of instant self-book.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "requiresApproval": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "requiresApproval"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMyBookingApprovalPolicy"
      }
    },
    "/booking-requests": {
      "get": {
        "tags": [
          "Booking requests"
        ],
        "summary": "List pending booking requests",
        "description": "Owners, admins, and dispatchers can list pending requests for their organization. Also returns recently approved public guests who have not yet been invited as a student or renter, and pick-aircraft holds that are still waiting on email confirmation (without the guest's name, email, or phone).",
        "responses": {
          "200": {
            "description": "The pending booking requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingRequest"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listPendingBookingRequests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Submit a booking request",
        "description": "Creates a pending booking request when org policy requires approval for this member.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid or approval is not required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createBookingRequest",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The requested window and personnel.",
                "properties": {
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "timeZoneName": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "resource": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "rating": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "personnel": {
                    "type": "object"
                  }
                },
                "required": [
                  "start",
                  "end",
                  "type"
                ]
              }
            }
          }
        }
      }
    },
    "/booking-requests/me": {
      "get": {
        "tags": [
          "Booking requests"
        ],
        "summary": "List my booking requests",
        "description": "Lists booking requests submitted by the signed-in member.",
        "responses": {
          "200": {
            "description": "Your booking requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingRequest"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMyBookingRequests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/booking-requests/{id}": {
      "get": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Get a booking request",
        "description": "Desk staff or the requester can read one booking request.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Booking request not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getBookingRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking request id."
          }
        ]
      }
    },
    "/booking-requests/{id}/approve": {
      "post": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Approve a booking request",
        "description": "Desk staff approves a pending request and creates the corresponding reservation.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The request is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "approveBookingRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking request id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional assignment overrides.",
                "properties": {
                  "resource": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "instructorOrgUserId": {
                    "type": "integer"
                  },
                  "personnel": {
                    "type": "object"
                  },
                  "title": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/booking-requests/{id}/reject": {
      "post": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Reject a booking request",
        "description": "Desk staff declines a pending request without touching the schedule.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The request is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "rejectBookingRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking request id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Optional reason shown to the member.",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/booking-requests/{id}/cancel": {
      "post": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Cancel a booking request",
        "description": "The requester or desk staff cancels a pending request.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The request is no longer pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "cancelBookingRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking request id."
          }
        ]
      }
    },
    "/booking-requests/{id}/convert": {
      "post": {
        "tags": [
          "Booking requests"
        ],
        "summary": "Invite a public guest as a member",
        "description": "After a public guest request is approved, desk staff can send a student or renter invitation to the guest email. Demo organizations are refused.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRequest"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Only an approved public guest request can be converted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Approve the request before inviting them as a member.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "convertBookingRequestGuest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking request id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The membership to offer.",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "student",
                      "renter"
                    ]
                  }
                },
                "required": [
                  "role"
                ]
              }
            }
          }
        }
      }
    },
    "/booking-offerings/visibility": {
      "get": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "Get calendar visibility",
        "description": "Returns per-audience calendar detail levels for the organization.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationCalendarVisibility"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getOrganizationCalendarVisibility"
      },
      "patch": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "Update calendar visibility",
        "description": "Owners and admins set what each audience tier sees on calendars and slot pickers.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrganizationCalendarVisibility"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only owners and admins may change visibility.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateOrganizationCalendarVisibility",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Audience levels to change.",
                "properties": {
                  "guestLevel": {
                    "type": "string"
                  },
                  "studentLevel": {
                    "type": "string"
                  },
                  "renterLevel": {
                    "type": "string"
                  },
                  "instructorLevel": {
                    "type": "string"
                  },
                  "memberLevel": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/booking-offerings": {
      "get": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "List booking offerings",
        "description": "Owners and admins list presets used for guest pages and internal slot discovery.",
        "responses": {
          "200": {
            "description": "The booking offerings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingOffering"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listBookingOfferings",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "Create a booking offering",
        "description": "Defines a bookable preset over org calendar policy. Offerings may only tighten org policy.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingOffering"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only owners and admins may manage offerings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createBookingOffering",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The offering to create.",
                "properties": {
                  "slug": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "reservationType": {
                    "type": "string"
                  },
                  "assignmentMode": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "description": {
                    "type": "string"
                  },
                  "locationId": {
                    "type": "integer"
                  },
                  "resourceIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "instructorOrgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "minimumNoticeMinutes": {
                    "type": "integer"
                  },
                  "bookingHorizonDays": {
                    "type": "integer"
                  },
                  "fixedReservationMinutes": {
                    "type": "integer"
                  },
                  "allowResourceChoice": {
                    "type": "boolean"
                  },
                  "collectionStyle": {
                    "type": "string",
                    "enum": [
                      "close_out",
                      "prepaid_fixed"
                    ]
                  },
                  "prepaidAmountCents": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                },
                "required": [
                  "slug",
                  "name",
                  "reservationType"
                ]
              }
            }
          }
        }
      }
    },
    "/booking-offerings/{id}": {
      "get": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "Get a booking offering",
        "description": "Returns one offering with eligible resources and instructors.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingOffering"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Booking offering not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getBookingOffering",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking offering id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "Update a booking offering",
        "description": "Owners and admins change offering settings or eligible resources.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingOffering"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only owners and admins may manage offerings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateBookingOffering",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking offering id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "active": {
                    "type": "boolean"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "reservationType": {
                    "type": "string"
                  },
                  "assignmentMode": {
                    "type": "string"
                  },
                  "locationId": {
                    "type": "integer"
                  },
                  "resourceIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "instructorOrgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "minimumNoticeMinutes": {
                    "type": "integer"
                  },
                  "bookingHorizonDays": {
                    "type": "integer"
                  },
                  "fixedReservationMinutes": {
                    "type": "integer"
                  },
                  "bufferBeforeMinutes": {
                    "type": "integer"
                  },
                  "bufferAfterMinutes": {
                    "type": "integer"
                  },
                  "maxReservationMinutes": {
                    "type": "integer"
                  },
                  "allowResourceChoice": {
                    "type": "boolean"
                  },
                  "collectionStyle": {
                    "type": "string",
                    "enum": [
                      "close_out",
                      "prepaid_fixed"
                    ]
                  },
                  "prepaidAmountCents": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "questions": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        }
      }
    },
    "/booking-offerings/{id}/slots": {
      "get": {
        "tags": [
          "Booking offerings"
        ],
        "summary": "List offering slots",
        "description": "Computes bookable slots for an offering, projected for the signed-in member's audience tier.",
        "responses": {
          "200": {
            "description": "Projected bookable slots for the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookableSlot"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "startDate and endDate are required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listBookingOfferingSlots",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The booking offering id."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window start (inclusive)."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window end (inclusive)."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/public/book/{orgSlug}/offerings/{offeringSlug}": {
      "get": {
        "tags": [
          "Public booking"
        ],
        "summary": "Get a public offering page",
        "description": "Unauthenticated. Returns organization name and offering details for a shareable guest booking link. Hidden unless public booking is enabled and the offering is active.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublicBookingPage"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Public booking is off, or this offering is not published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getPublicBookingOffering",
        "security": [],
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The organization's public booking slug."
          },
          {
            "name": "offeringSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The offering slug."
          }
        ]
      }
    },
    "/public/book/{orgSlug}/offerings/{offeringSlug}/slots": {
      "get": {
        "tags": [
          "Public booking"
        ],
        "summary": "List public offering slots",
        "description": "Unauthenticated. Bookable windows for one offering, projected for guests (open times only by default).",
        "responses": {
          "200": {
            "description": "Guest-safe bookable slots.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookableSlot"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "startDate and endDate are required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Public booking is off, or this offering is not published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listPublicBookingOfferingSlots",
        "security": [],
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The organization's public booking slug."
          },
          {
            "name": "offeringSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The offering slug."
          },
          {
            "name": "startDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window start."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window end."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/public/book/{orgSlug}/offerings/{offeringSlug}/requests": {
      "post": {
        "tags": [
          "Public booking"
        ],
        "summary": "Submit a public booking request",
        "description": "Unauthenticated. Creates an unverified guest request and emails a confirmation link. The desk queue only sees the request after the guest confirms. When the offering lets the guest pick an aircraft, that tail is held from submit until the request is approved, declined, or expired. Desk-assigns offerings do not hold a specific tail.",
        "responses": {
          "200": {
            "description": "Check your email to confirm.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "submitted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing name, email, phone, consent, or an invalid slot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That slot is gone, or a request for this email and time already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createPublicBookingRequest",
        "security": [],
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The organization's public booking slug."
          },
          {
            "name": "offeringSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The offering slug."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Guest request.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "end": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "timeZoneName": {
                    "type": "string"
                  },
                  "resourceId": {
                    "type": "integer"
                  },
                  "consent": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "email",
                  "phone",
                  "start",
                  "end",
                  "consent"
                ]
              }
            }
          }
        }
      }
    },
    "/public/book/confirm": {
      "post": {
        "tags": [
          "Public booking"
        ],
        "summary": "Confirm a public booking request",
        "description": "Unauthenticated. Promotes an unverified guest request to pending after the email link is opened.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "confirmed": {
                          "type": "boolean"
                        },
                        "alreadyConfirmed": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or expired token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The request can no longer be confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "confirmPublicBookingRequest",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Confirmation token from the email link.",
                "properties": {
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        }
      }
    },
    "/currencies": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "List my currencies",
        "description": "The signed-in member's standing against every tracked requirement.",
        "responses": {
          "200": {
            "description": "The currency records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Currency"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCurrencies",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/currencies/{currencyId}": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "Get a currency record",
        "description": "Note this takes a CURRENCY id, not a currency-type id , the two are one path segment apart and are easy to confuse.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Currency"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not yours to see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such currency.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCurrency",
        "parameters": [
          {
            "name": "currencyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Currencies"
        ],
        "summary": "Update a currency record",
        "description": "Adjusts the dates on one member's standing.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Currency"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not yours to edit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateCurrency",
        "parameters": [
          {
            "name": "currencyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {}
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Currencies"
        ],
        "summary": "Renew a currency",
        "description": "Signs off a renewal and starts a new period. Who may do this is governed by the currency type's `canRenewSelf` / `instructorCanRenew` / `dispatcherCanRenew` flags.",
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Currency"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You may not renew this one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "renewCurrency",
        "parameters": [
          {
            "name": "currencyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The renewal.",
                "properties": {
                  "completedAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/currencies/types/{currencyTypeId}/currencies": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "List a type's currency records",
        "description": "Everyone's standing against one requirement.",
        "responses": {
          "200": {
            "description": "The currency records.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Currency"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCurrenciesForType",
        "parameters": [
          {
            "name": "currencyTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency type id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/currencies/types/{currencyTypeId}/detail": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "Get a currency rule detail",
        "description": "The rule, status totals, and a page of member standings with attached and missing document evidence.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CurrencyRuleDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Only administrators and dispatchers in this organization may view the roster.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such currency rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCurrencyRuleDetail",
        "parameters": [
          {
            "name": "currencyTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency type id."
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search member name or email.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only return members in this standing.",
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "expiring",
                "expired",
                "notSignedOff"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum member rows to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Member rows to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ]
      }
    },
    "/userDocuments": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Upload a document",
        "description": "Records a document against a member. Administrators may upload on someone else's behalf; whether a member can upload their own depends on the document type's `restricted` flag.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDocument"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createUserDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The document.",
                "properties": {
                  "documentTypeId": {
                    "type": "integer"
                  },
                  "orgUserId": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "documentTypeId",
                  "orgUserId"
                ]
              }
            }
          }
        }
      }
    },
    "/userDocuments/{documentId}": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Get a document",
        "description": "One document record.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserDocument",
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Documents"
        ],
        "summary": "Update a document",
        "description": "Renames it or changes its expiry.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateUserDocument",
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document",
        "description": "Removes the document.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteUserDocument",
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document id."
          }
        ]
      }
    },
    "/userDocuments/{documentId}/signedUrl": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Get a signed URL for a document",
        "description": "Returns a short-lived URL to upload or download the file. Documents are not served from a public URL.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getUserDocumentSignedUrl",
        "parameters": [
          {
            "name": "documentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "No fields required.",
                "properties": {}
              }
            }
          }
        }
      }
    },
    "/userDocuments/orgUsers/{orgUserId}": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "List a member's documents",
        "description": "Everything held against one member.",
        "responses": {
          "200": {
            "description": "The documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserDocument"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listUserDocumentsForMember",
        "parameters": [
          {
            "name": "orgUserId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The membership id."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      }
    },
    "/reports/catalog": {
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "List available reports",
        "description": "Every report you may run, with its id and category. **Start here**: the ids returned are what `POST /reports/run` and `POST /reports/export` take, and the catalog is filtered to what your roles allow, so you never see a report you cannot run.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "timeZone": {
                          "type": "string"
                        },
                        "categories": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "reports": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ReportDefinition"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReports"
      }
    },
    "/reports/run": {
      "post": {
        "tags": [
          "Reports"
        ],
        "summary": "Run a report",
        "description": "Runs one report from the catalog and returns columns, rows, and totals.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ReportResult"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown report id, or an unparseable window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "runReport",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which report, over what window.",
                "properties": {
                  "reportId": {
                    "type": "string",
                    "example": "revenue"
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "config": {
                    "type": "object"
                  }
                },
                "required": [
                  "reportId"
                ]
              }
            }
          }
        }
      }
    },
    "/reports/export": {
      "post": {
        "tags": [
          "Reports"
        ],
        "summary": "Export a report as CSV",
        "description": "Same inputs as `run`, but the response is `text/csv` rather than the usual JSON envelope.",
        "responses": {
          "200": {
            "description": "The report as CSV.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Unknown report id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "exportReport",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Which report, over what window.",
                "properties": {
                  "reportId": {
                    "type": "string"
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "csv"
                    ]
                  }
                },
                "required": [
                  "reportId"
                ]
              }
            }
          }
        }
      }
    },
    "/reports/overview": {
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "Organization overview",
        "description": "The headline numbers for a window , the figures behind the dashboard.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getReportOverview",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Start of the window, ISO 8601. Include an offset or `Z`."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "End of the window, ISO 8601."
          }
        ]
      }
    },
    "/search": {
      "get": {
        "tags": [
          "Search"
        ],
        "summary": "Search across the organization",
        "description": "One search over aircraft, members, bookings, locations, maintenance, compliance and training. The results are narrowed to what your roles allow, per entity type , so this is safe to expose to any member.\n\n`q` is optional: omitting it returns the newest few of each type, which is what a search screen shows before anyone types.\n\nThe response's `types` is the list this caller may search at all, which is not the same as the types that matched: use it for a category filter, and never assume a type is in it.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SearchResults"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "search",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text filter. Matches the fields a person would search on."
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "person,resource"
            },
            "description": "Comma-separated entity types to search. Unknown names are ignored. Defaults to all."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Maximum matches per type."
          }
        ]
      }
    },
    "/weather/metar": {
      "get": {
        "tags": [
          "Weather"
        ],
        "summary": "Current METAR",
        "description": "The latest observation for a field. Prefer `airport` when the location has a stored identifier; `lat`/`lon` are the fallback for a typed site, and for a strip whose ident has no METAR.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A valid airport identifier or lat and lon are required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMetar",
        "parameters": [
          {
            "name": "airport",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "example": "KAPA"
            },
            "description": "Airport identifier. ICAO when the field has one, otherwise the FAA local code."
          },
          {
            "name": "lat",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "example": 39.5701
            },
            "description": "Latitude. Used when `airport` is omitted, or when that station has no METAR."
          },
          {
            "name": "lon",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "example": -104.8493
            },
            "description": "Longitude."
          }
        ]
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Service"
        ],
        "summary": "Health check",
        "description": "Answers 200 while the service is up. Not rate limited, and does not use the standard `{ data }` envelope.",
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "health": {
                      "type": "string",
                      "example": "server is running"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/widgets/snapshot": {
      "get": {
        "tags": [
          "Widgets"
        ],
        "summary": "Everything a home-screen widget renders",
        "description": "One read that answers \"what does this person need to see right now\": their next flights, their expiring currencies, what they owe, the fleet's day, and, for staff, the maintenance queue.\n\nBuilt for the AerScheduler iOS widgets, which poll it on a timer, so it is deliberately one round trip and every list is capped at six rows (twelve for the fleet). It is the same set of facts the other endpoints serve, already reduced to what fits on a card, which is also what makes it a reasonable thing to poll from a dashboard or a wall display.\n\n**Any member may call it, and the shape depends on who they are.** `squawks` and `inspections` are OMITTED rather than zeroed for a caller who may not read maintenance, because zero open squawks and no permission to see them are different facts and a widget must not paint the second as the first. Check `canSeeMaintenance`. `training` is present only for a student with a live enrollment, and `instructor` only for an instructor.\n\n`fleet` is the whole organization's day, not just the caller's bookings, which is what makes an aircraft's `status` true.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WidgetSnapshot"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getWidgetSnapshot"
      }
    },
    "/realtime/ticket": {
      "post": {
        "tags": [
          "Realtime"
        ],
        "summary": "Mint a WebSocket ticket",
        "description": "Returns a short-lived, single-use ticket. Connect to `/realtime/ws` (see `path` / `url`) and send `{ \"v\": 1, \"type\": \"auth\", \"ticket\": \"…\" }` as the first frame so the ticket never sits in a URL. Prefer this over putting a JWT in the WebSocket URL. The upgrade itself is not part of this OpenAPI document.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "ticket",
                        "expiresAt",
                        "url",
                        "path"
                      ],
                      "properties": {
                        "ticket": {
                          "type": "string",
                          "description": "Single-use ticket, typically valid for about 60 seconds. Send it in the first WebSocket frame."
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "url": {
                          "type": "string",
                          "description": "Absolute WebSocket URL without a ticket query parameter."
                        },
                        "path": {
                          "type": "string",
                          "description": "Path-only WebSocket endpoint (`/realtime/ws`). Prefer building the URL from the same origin/proxy as the REST API."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a member of an organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many ticket requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createRealtimeTicket"
      }
    },
    "/locations": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "List locations",
        "description": "Every location in your organization.",
        "responses": {
          "200": {
            "description": "The locations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listLocations",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "summary": "Create a location",
        "description": "Adds a location to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The location that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Location"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createLocation",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new location. Picking a published airport should send its `ident` and `coordinates`. A typed site can omit both. The address is stored as entered.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "ident": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "ICAO when the field has one (`KAPA`), otherwise the FAA local code. Optional."
                  },
                  "timeZone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "$ref": "#/components/schemas/Address"
                  },
                  "coordinates": {
                    "type": "object",
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    }
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/locations/{id}": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "Get a location",
        "description": "Fetches one location by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Location"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such location, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getLocation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The location id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Locations"
        ],
        "summary": "Update a location",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated location.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Location"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateLocation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The location id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change. Omitting `ident` leaves the stored identifier alone; send null to clear it. Same rule for `timeZone`. Omitting `coordinates` leaves the stored position alone.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "ident": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "timeZone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "address": {
                    "$ref": "#/components/schemas/Address"
                  },
                  "coordinates": {
                    "type": "object",
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "summary": "Delete a location",
        "description": "Removes the location. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The location could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteLocation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The location id."
          }
        ]
      }
    },
    "/resources": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List resources",
        "description": "Every bookable resource , aircraft, simulators, and rooms together. Use `/resources/aircraft`, `/simulators`, or `/rooms` for one kind.",
        "responses": {
          "200": {
            "description": "The resources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Resource"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listResources",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Create a resource",
        "description": "Adds a resource to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The resource that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Resource"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createResource",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The new resource. Exactly one of `type.plane`, `type.simulator`, or `type.room` must be present, and `location.id` is required.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "use": {
                    "type": "string",
                    "enum": [
                      "fleet",
                      "shop"
                    ],
                    "description": "`fleet` (default) is one of your own: schedulable, and counted on your per-aircraft plan. `shop` is a customer's aircraft in for maintenance: it takes maintenance bookings only, it is never offered publicly, and it is NOT counted on your plan. Anything unrecognised reads as `fleet`."
                  },
                  "type": {
                    "type": "object",
                    "properties": {
                      "plane": {
                        "$ref": "#/components/schemas/Plane"
                      },
                      "simulator": {
                        "type": "object"
                      },
                      "room": {
                        "type": "object"
                      }
                    }
                  }
                },
                "required": [
                  "name",
                  "location",
                  "type"
                ]
              }
            }
          }
        }
      }
    },
    "/resources/{id}": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "Get a resource",
        "description": "Fetches one resource by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Resource"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getResource",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Resources"
        ],
        "summary": "Update a resource",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated resource.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Resource"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateResource",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change. `type` must be present even when you are only changing a top-level field, and it has to be the kind the resource already is: this cannot turn an aircraft into a room. `use` moves an aircraft between your fleet and your shop. Moving one INTO the shop is refused while it still has upcoming bookings that are not maintenance, because those people would be stranded; moving it out simply makes it schedulable and billable again. **Omitting a key leaves the stored value alone, and on most fields `null` does the same. Four rules break that pattern, and each one is silent, because the write answers 200 whether it changed the field, left it alone, or blanked one nobody meant to blank.** On `serialNumber` and `year`, `\"\"` or `null` CLEARS the stored value. That is the only way back to blank once a wrong serial number has been typed in. On the pair `flyingDayStartMinute` and `flyingDayEndMinute`, `null` is taken literally and puts the aircraft back on the organization's hours, which changes the bookings the server will accept. Send both or neither; a half pair is rejected. On `cost.dryRate`, `cost.wetRate` and `cost.overnightMinimumTenths`, `null` is literal too, and it is not `0`: a `null` overnight minimum goes back to inheriting the organization's figure, while `0` exempts this aircraft. `groundedReason` is not independent of `grounded`. Any aircraft or simulator update that does not carry `grounded: true` writes the reason back to `null`, so send the two together. One value is not merely surprising but rejected: `cost.billByHobbsTime` is not nullable, and sending `null` fails the whole request with a 400. Omit the key to leave it alone. On the free-text fields (`tailNumber`, `make`, `model`) an empty string is stored AS an empty string and does not clear them.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "location": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      }
                    }
                  },
                  "type": {
                    "type": "object",
                    "properties": {
                      "plane": {
                        "$ref": "#/components/schemas/Plane"
                      },
                      "simulator": {
                        "type": "object"
                      },
                      "room": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Delete a resource",
        "description": "Removes the resource. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The resource could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteResource",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource id."
          }
        ]
      }
    },
    "/currencies/types": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "List currency types",
        "description": "Every currency type in your organization.",
        "responses": {
          "200": {
            "description": "The currency types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CurrencyType"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listCurrencyTypes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Currencies"
        ],
        "summary": "Create a currency type",
        "description": "Adds a currency type to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The currency type that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CurrencyType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createCurrencyType",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The requirement to track. Attaching member groups is what actually creates the per-member `Currency` records , a type created with no groups tracks nobody.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "expiresInMonths": {
                    "type": "integer"
                  },
                  "expiresInDays": {
                    "type": "integer"
                  },
                  "warningPeriodInDays": {
                    "type": "integer"
                  },
                  "canRenewSelf": {
                    "type": "boolean"
                  },
                  "instructorCanRenew": {
                    "type": "boolean"
                  },
                  "dispatcherCanRenew": {
                    "type": "boolean"
                  },
                  "orgUserGroupIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/currencies/types/{currencyTypeId}": {
      "get": {
        "tags": [
          "Currencies"
        ],
        "summary": "Get a currency type",
        "description": "Fetches one currency type by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CurrencyType"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such currency type, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getCurrencyType",
        "parameters": [
          {
            "name": "currencyTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency type id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Currencies"
        ],
        "summary": "Update a currency type",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated currency type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CurrencyType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateCurrencyType",
        "parameters": [
          {
            "name": "currencyTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency type id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "expiresInMonths": {
                    "type": "integer"
                  },
                  "orgUserGroupIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Currencies"
        ],
        "summary": "Delete a currency type",
        "description": "Removes the currency type. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The currency type could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteCurrencyType",
        "parameters": [
          {
            "name": "currencyTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The currency type id."
          }
        ]
      }
    },
    "/userDocuments/types": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "List document types",
        "description": "Every document type in your organization.",
        "responses": {
          "200": {
            "description": "The document types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DocumentType"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listDocumentTypes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Create a document type",
        "description": "Adds a document type to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The document type that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DocumentType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createDocumentType",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The category. `restricted: true` means only administrators may upload documents of this type.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "restricted": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/userDocuments/types/{documentTypeId}": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Get a document type",
        "description": "Fetches one document type by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DocumentType"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such document type, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getDocumentType",
        "parameters": [
          {
            "name": "documentTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document type id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Documents"
        ],
        "summary": "Update a document type",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated document type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DocumentType"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateDocumentType",
        "parameters": [
          {
            "name": "documentTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document type id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "restricted": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document type",
        "description": "Removes the document type. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The document type could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteDocumentType",
        "parameters": [
          {
            "name": "documentTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The document type id."
          }
        ]
      }
    },
    "/groups/orgUser": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "List member groups",
        "description": "Every member group in your organization.",
        "responses": {
          "200": {
            "description": "The member groups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Group"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMemberGroups",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Groups"
        ],
        "summary": "Create a member group",
        "description": "Adds a member group to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The member group that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createMemberGroup",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The group. The `addNew*` flags auto-enroll future members holding that role.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "orgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "addNewUsers": {
                    "type": "boolean"
                  },
                  "addNewStudents": {
                    "type": "boolean"
                  },
                  "addNewInstructors": {
                    "type": "boolean"
                  },
                  "addNewRenters": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/groups/orgUser/{orgUserGroupId}": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "Get a member group",
        "description": "Fetches one member group by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such member group, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMemberGroup",
        "parameters": [
          {
            "name": "orgUserGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member group id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Groups"
        ],
        "summary": "Update a member group",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated member group.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateMemberGroup",
        "parameters": [
          {
            "name": "orgUserGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member group id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "orgUserIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Groups"
        ],
        "summary": "Delete a member group",
        "description": "Removes the member group. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The member group could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteMemberGroup",
        "parameters": [
          {
            "name": "orgUserGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The member group id."
          }
        ]
      }
    },
    "/groups/resource": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "List resource groups",
        "description": "Every resource group in your organization.",
        "responses": {
          "200": {
            "description": "The resource groups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Group"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listResourceGroups",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Groups"
        ],
        "summary": "Create a resource group",
        "description": "Adds a resource group to your organization. Requires the **admin** role.",
        "responses": {
          "201": {
            "description": "The resource group that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createResourceGroup",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The group.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "resourceIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/groups/resource/{resourceGroupId}": {
      "get": {
        "tags": [
          "Groups"
        ],
        "summary": "Get a resource group",
        "description": "Fetches one resource group by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource group, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getResourceGroup",
        "parameters": [
          {
            "name": "resourceGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource group id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Groups"
        ],
        "summary": "Update a resource group",
        "description": "Changes the fields you send and leaves the rest alone. Requires the **admin** role.",
        "responses": {
          "200": {
            "description": "The updated resource group.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Group"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateResourceGroup",
        "parameters": [
          {
            "name": "resourceGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource group id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "resourceIds": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Groups"
        ],
        "summary": "Delete a resource group",
        "description": "Removes the resource group. Requires the **admin** role.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The resource group could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteResourceGroup",
        "parameters": [
          {
            "name": "resourceGroupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The resource group id."
          }
        ]
      }
    },
    "/maintenance/reminders/templates": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "List reminder templates",
        "description": "Every reminder template in your organization.",
        "responses": {
          "200": {
            "description": "The reminder templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MaintenanceReminderTemplate"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listMaintenanceReminderTemplates",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Create a reminder template",
        "description": "Adds a reminder template to your organization.",
        "responses": {
          "201": {
            "description": "The reminder template that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminderTemplate"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createMaintenanceReminderTemplate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The rule. Set `remindMonths` or `remindDays` for the calendar clock, `remindHours` for the meter clock, or BOTH clocks (\"every 100 hours or 12 calendar months, whichever comes first\"), each with its matching `*Before` field; or set `remindDate` alone for a one-off date, or `remindAtHours` alone for a one-off meter deadline. Neither one-off recurs. `remindHours` is an INTERVAL in tenths of an hour (1000 = every 100.0), not a flag. PREFER `remindMonths` for anything the regulations write in calendar months, which is the annual, the transponder, the static system and the ELT: a calendar month runs to the end of the month, so 365 days comes due up to a month early. Setting `remindMonths` overwrites `remindDays` with a derived approximation. On a combined interval whichever clock comes due first grounds and notifies, and signing off restarts both.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "repeat": {
                    "type": "boolean"
                  },
                  "ground": {
                    "type": "boolean"
                  },
                  "remindHours": {
                    "type": "integer",
                    "example": 100
                  },
                  "remindHoursBefore": {
                    "type": "integer"
                  },
                  "hourBasedOn": {
                    "type": "string",
                    "enum": [
                      "tach",
                      "hobbs"
                    ]
                  },
                  "remindDays": {
                    "type": "integer"
                  },
                  "remindMonths": {
                    "type": "integer",
                    "example": 12
                  },
                  "remindDaysBefore": {
                    "type": "integer"
                  },
                  "remindDate": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "remindAtHours": {
                    "type": "integer",
                    "example": 12500
                  },
                  "templateResources": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "startHour": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "reminderSteps": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "description": "When it is said, and to whom (Murray spec 6): `shopDays` and `ownerDays`, days before a calendar due date; `shopHours` and `ownerHours`, TENTHS of an hour before an hour one. Each a list of up to 6 whole numbers (days 1 to 365; tenths 1 to 10000). An absent key is the default: the shop's single `remindDaysBefore` / `remindHoursBefore`, the owners' 30 and 7 days and 10 hours. An empty list is none before it is due. Due always goes out, and the shop hears once more a day after it is overdue. Setting the shop's lists also sets `remindDaysBefore` / `remindHoursBefore` to their furthest step. Null puts every list back on its default.",
                    "properties": {
                      "shopDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "shopHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "required": [
                  "name",
                  "repeat"
                ]
              }
            }
          }
        }
      }
    },
    "/maintenance/reminders/templates/{maintenanceReminderTemplateId}": {
      "get": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Get a reminder template",
        "description": "Fetches one reminder template by id.",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminderTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such reminder template, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getMaintenanceReminderTemplate",
        "parameters": [
          {
            "name": "maintenanceReminderTemplateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder template id."
          }
        ]
      },
      "patch": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Update a reminder template",
        "description": "Changes the fields you send and leaves the rest alone.",
        "responses": {
          "200": {
            "description": "The updated reminder template.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MaintenanceReminderTemplate"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateMaintenanceReminderTemplate",
        "parameters": [
          {
            "name": "maintenanceReminderTemplateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder template id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "repeat": {
                    "type": "boolean"
                  },
                  "reminderSteps": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "description": "When it is said, and to whom (Murray spec 6): `shopDays` and `ownerDays`, days before a calendar due date; `shopHours` and `ownerHours`, TENTHS of an hour before an hour one. Each a list of up to 6 whole numbers (days 1 to 365; tenths 1 to 10000). An absent key is the default: the shop's single `remindDaysBefore` / `remindHoursBefore`, the owners' 30 and 7 days and 10 hours. An empty list is none before it is due. Due always goes out, and the shop hears once more a day after it is overdue. Setting the shop's lists also sets `remindDaysBefore` / `remindHoursBefore` to their furthest step. Null puts every list back on its default.",
                    "properties": {
                      "shopDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "shopHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerDays": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      "ownerHours": {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Maintenance"
        ],
        "summary": "Delete a reminder template",
        "description": "Removes the reminder template.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The reminder template could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteMaintenanceReminderTemplate",
        "parameters": [
          {
            "name": "maintenanceReminderTemplateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The reminder template id."
          }
        ]
      }
    },
    "/reports/views": {
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "List saved report views",
        "description": "Every saved report view in your organization.",
        "responses": {
          "200": {
            "description": "The saved report views.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReportDefinition"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "listReportDefinitions",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1000
            },
            "description": "Rows to return, 1,1000. Defaults to 1000; a larger value is clamped rather than rejected."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Rows to skip, for paging. Defaults to 0."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Field to order by before paging, as a dot path into the row , `total`, `user.firstName`. Omit to keep the endpoint's own order. Numbers and ISO timestamps order as numbers and instants, not as text, and empty values always sort last regardless of direction. Ordering happens before the page is cut, so it orders the whole collection rather than the page you are holding."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "`asc` (default) or `desc`. Only meaningful with `sort`."
          }
        ]
      },
      "post": {
        "tags": [
          "Reports"
        ],
        "summary": "Create a saved report view",
        "description": "Adds a saved report view to your organization.",
        "responses": {
          "201": {
            "description": "The saved report view that was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ReportDefinition"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation. `message` says which field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "createReportDefinition",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "The saved view.",
                "properties": {
                  "reportId": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "config": {
                    "type": "object"
                  },
                  "isShared": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "reportId",
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/reports/views/{viewId}": {
      "patch": {
        "tags": [
          "Reports"
        ],
        "summary": "Update a saved report view",
        "description": "Changes the fields you send and leaves the rest alone.",
        "responses": {
          "200": {
            "description": "The updated saved report view.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ReportDefinition"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "updateReportDefinition",
        "parameters": [
          {
            "name": "viewId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The saved view id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Fields to change.",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "config": {
                    "type": "object"
                  },
                  "isShared": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Reports"
        ],
        "summary": "Delete a saved report view",
        "description": "Removes the saved report view.",
        "responses": {
          "204": {
            "description": "Success. No body."
          },
          "400": {
            "description": "The saved report view could not be deleted , usually because something still references it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No token, an expired token, a malformed one, or a session token naming a membership that is no longer yours (\"Your session is out of date\"). Sign in again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Authenticated, but not allowed to do this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteReportDefinition",
        "parameters": [
          {
            "name": "viewId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The saved view id."
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "AerScheduler API key",
        "description": "An API key, sent as `Authorization: Bearer ask_live_…`.\n\nCreate one in the web console at **Settings → API keys**. The secret is shown once and stored only as a hash , if you lose it, revoke the key and create another.\n\nA key is scoped to one organization and carries the roles you gave it , every permission rule that applies to a member holding those roles applies to the key. Grant the least it needs."
      }
    },
    "schemas": {
      "Address": {
        "type": "object",
        "description": "A postal address.",
        "properties": {
          "streetAddress1": {
            "type": "string",
            "description": "Street address."
          },
          "streetAddress2": {
            "description": "Suite, unit, or hangar number.",
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": "string",
            "description": "City."
          },
          "state": {
            "type": "string",
            "description": "State or region."
          },
          "zipCode": {
            "type": "string",
            "description": "Postal code."
          },
          "country": {
            "type": "string",
            "description": "Country."
          },
          "coordinates": {
            "type": "object",
            "description": "Geocoded position, when the address could be resolved.",
            "properties": {
              "lat": {
                "type": "number",
                "description": "Latitude."
              },
              "lng": {
                "type": "number",
                "description": "Longitude."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ApiKey": {
        "type": "object",
        "description": "A credential that lets software act on your organization's behalf. The secret is never included, only a hash is stored.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "How you recognise it.",
            "example": "Zapier sync"
          },
          "prefix": {
            "type": "string",
            "description": "The displayable head of the key. Not a secret, and not enough to reconstruct one.",
            "example": "ask_live_7f3a9c"
          },
          "roles": {
            "type": "array",
            "description": "What the key can do. These are real roles, every rule that applies to a member holding them applies to the key.",
            "items": {
              "type": "string",
              "enum": [
                "admin",
                "dispatcher",
                "instructor",
                "student",
                "renter",
                "technician"
              ]
            }
          },
          "status": {
            "type": "string",
            "description": "Whether the key currently works.",
            "enum": [
              "active",
              "expired",
              "revoked"
            ]
          },
          "expiresAt": {
            "format": "date-time",
            "description": "When it stops working. Null means no expiry.",
            "type": [
              "string",
              "null"
            ]
          },
          "revokedAt": {
            "format": "date-time",
            "description": "When it was revoked. Null while live.",
            "type": [
              "string",
              "null"
            ]
          },
          "lastUsedAt": {
            "format": "date-time",
            "description": "When it last authenticated a request, to the nearest minute. Null if never used, which is how you find keys to turn off.",
            "type": [
              "string",
              "null"
            ]
          },
          "createdBy": {
            "type": "object",
            "description": "The administrator who minted it. Null if they have since left.",
            "properties": {},
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ApiKeyWithSecret": {
        "type": "object",
        "description": "A newly created key. This is the ONLY response that ever contains the secret, store it now.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "How you recognise it."
          },
          "prefix": {
            "type": "string",
            "description": "The displayable head of the key."
          },
          "roles": {
            "type": "array",
            "description": "What the key can do.",
            "items": {
              "type": "string"
            }
          },
          "expiresAt": {
            "format": "date-time",
            "description": "When it stops working.",
            "type": [
              "string",
              "null"
            ]
          },
          "secret": {
            "type": "string",
            "description": "The key itself. Send it as `Authorization: Bearer <secret>`. We store only a hash, so if you lose this you must revoke the key and mint another.",
            "example": "ask_live_kJ8vN2pQxR4tY7wA1sD3fG5hJ6kL9mZ0"
          }
        },
        "additionalProperties": true
      },
      "Pagination": {
        "type": "object",
        "description": "Where this page sits in the whole collection. Present on every list response.",
        "properties": {
          "total": {
            "type": "integer",
            "description": "How many there are in all, before this page was taken."
          },
          "limit": {
            "type": "integer",
            "description": "Rows requested for this page. 1000 unless you asked for fewer."
          },
          "offset": {
            "type": "integer",
            "description": "Rows skipped to reach this page."
          },
          "returned": {
            "type": "integer",
            "description": "Rows actually in `data`."
          },
          "hasMore": {
            "type": "boolean",
            "description": "Whether another page exists. Check this, some collections are larger than one page."
          }
        },
        "additionalProperties": true
      },
      "Organization": {
        "type": "object",
        "description": "A flight school, club, or FBO. Every other record belongs to exactly one.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "code": {
            "type": "string",
            "description": "Six-character join code members use to request access.",
            "example": "AERTEST01"
          },
          "organizationType": {
            "description": "Free-text category, e.g. `flight school`.",
            "type": [
              "string",
              "null"
            ]
          },
          "about": {
            "description": "Public description.",
            "type": [
              "string",
              "null"
            ]
          },
          "showInDirectory": {
            "type": "boolean",
            "description": "Whether the organization opts into the public directory."
          },
          "plan": {
            "type": "string",
            "description": "Which plan the organization is on: `standard` or `enterprise`. Enterprise is what makes this API available at all.",
            "example": "enterprise"
          },
          "scheduledDeletionAt": {
            "format": "date-time",
            "description": "When set, the organization will be hard-deleted at this time. Null means no countdown is running. Owners schedule via DELETE /organizations; admins cancel via POST /organizations/cancelDeletion.",
            "type": [
              "string",
              "null"
            ]
          },
          "publicBookingEnabled": {
            "type": "boolean",
            "description": "When true, guests can request active offerings at /book/{publicBookingSlug}/{offering.slug}."
          },
          "publicBookingSlug": {
            "description": "Public share-link slug for guest booking pages.",
            "type": [
              "string",
              "null"
            ]
          },
          "publicBookingEmbedHosts": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "HTTPS origins allowed to iframe /book pages. Empty means the share link only. AerScheduler itself can always open the page. Present on settings PATCH responses; not returned by GET /organizations/{orgId}."
          },
          "publicBookingAccentHex": {
            "description": "Guest /book accent color, #RRGGBB.",
            "type": [
              "string",
              "null"
            ]
          },
          "publicBookingAppearance": {
            "type": "string",
            "description": "Guest page appearance: light, dark, or system."
          },
          "publicBookingDensity": {
            "type": "string",
            "description": "Guest page density: compact or comfortable."
          },
          "publicBookingCornerStyle": {
            "type": "string",
            "description": "Guest page corners: rounded or sharp."
          },
          "details": {
            "type": "object",
            "description": "Contact details.",
            "properties": {
              "email": {
                "description": "Primary contact email.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "description": "Primary contact phone.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "type": "object",
                "description": "A postal address.",
                "properties": {
                  "streetAddress1": {
                    "type": "string",
                    "description": "Street address."
                  },
                  "streetAddress2": {
                    "description": "Suite, unit, or hangar number.",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "city": {
                    "type": "string",
                    "description": "City."
                  },
                  "state": {
                    "type": "string",
                    "description": "State or region."
                  },
                  "zipCode": {
                    "type": "string",
                    "description": "Postal code."
                  },
                  "country": {
                    "type": "string",
                    "description": "Country."
                  },
                  "coordinates": {
                    "type": "object",
                    "description": "Geocoded position, when the address could be resolved.",
                    "properties": {
                      "lat": {
                        "type": "number",
                        "description": "Latitude."
                      },
                      "lng": {
                        "type": "number",
                        "description": "Longitude."
                      }
                    },
                    "additionalProperties": true
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "preferences": {
            "type": "object",
            "description": "Per-organization behaviour switches.",
            "properties": {},
            "additionalProperties": true
          },
          "locations": {
            "type": "array",
            "description": "Bases this organization operates from.",
            "items": {
              "$ref": "#/components/schemas/Location"
            }
          }
        },
        "additionalProperties": true
      },
      "Location": {
        "type": "object",
        "description": "A base of operations, a field, hangar, or classroom building.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "Main Hangar"
          },
          "ident": {
            "description": "Published airport identifier. ICAO when the field has one (`KAPA`), otherwise the FAA local code (`00A`). Null when the person typed a site by hand. Weather uses this when present instead of guessing the nearest reporting station from coordinates.",
            "example": "KAPA",
            "maxLength": 10,
            "type": [
              "string",
              "null"
            ]
          },
          "showInDirectory": {
            "type": "boolean",
            "description": "Whether this location appears in the public directory."
          },
          "timeZone": {
            "description": "IANA time zone of the field, e.g. `America/Denver`. Scheduling is anchored to the AIRPORT's zone, not the caller's device, see the Time zones section.",
            "example": "America/Denver",
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": "object",
            "description": "A postal address.",
            "properties": {
              "streetAddress1": {
                "type": "string",
                "description": "Street address."
              },
              "streetAddress2": {
                "description": "Suite, unit, or hangar number.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": "string",
                "description": "City."
              },
              "state": {
                "type": "string",
                "description": "State or region."
              },
              "zipCode": {
                "type": "string",
                "description": "Postal code."
              },
              "country": {
                "type": "string",
                "description": "Country."
              },
              "coordinates": {
                "type": "object",
                "description": "Geocoded position, when the address could be resolved.",
                "properties": {
                  "lat": {
                    "type": "number",
                    "description": "Latitude."
                  },
                  "lng": {
                    "type": "number",
                    "description": "Longitude."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Plane": {
        "type": "object",
        "description": "Aircraft-specific detail on a resource whose type is an aircraft.",
        "properties": {
          "tailNumber": {
            "type": "string",
            "description": "Registration, e.g. `N12345`.",
            "example": "N12345"
          },
          "make": {
            "type": "string",
            "description": "Manufacturer.",
            "example": "Cessna"
          },
          "model": {
            "type": "string",
            "description": "Model designation.",
            "example": "172S"
          },
          "year": {
            "description": "Model year, four digits.",
            "example": "2019",
            "type": [
              "string",
              "null"
            ]
          },
          "serialNumber": {
            "description": "The manufacturer's serial number from the data plate, which is what an Airworthiness Directive names its applicability by. Distinct from the registration: a tail number can be changed or reassigned, a serial number cannot.",
            "example": "172S9142",
            "maxLength": 40,
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string",
            "description": "What the aircraft is. Together with `aircraftClass` this replaces the four-value `categoryClass`, which could not describe a helicopter.",
            "enum": [
              "airplane",
              "rotorcraft",
              "glider",
              "lighter_than_air",
              "powered_lift",
              "powered_parachute",
              "weight_shift_control"
            ]
          },
          "aircraftClass": {
            "description": "Class within the category. Null where the category has no class rating (a glider) or nobody has said. Only classes belonging to the category are accepted: a rotorcraft takes `helicopter` or `gyroplane`, and any other pairing is rejected.",
            "enum": [
              "single_engine_land",
              "multi_engine_land",
              "single_engine_sea",
              "multi_engine_sea",
              "helicopter",
              "gyroplane",
              "airship",
              "balloon",
              "land",
              "sea",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "engineType": {
            "description": "Powerplant.",
            "enum": [
              "piston",
              "turboprop",
              "turboshaft",
              "turbojet",
              "turbofan",
              "electric",
              "none",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "fuelType": {
            "description": "Fuel grade.",
            "enum": [
              "avgas_100ll",
              "mogas",
              "jet_a",
              "diesel",
              "electric",
              "none",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "gearType": {
            "description": "Landing gear.",
            "enum": [
              "tricycle",
              "tailwheel",
              "skids",
              "floats",
              "amphibious",
              "skis",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "seats": {
            "description": "Occupants the airframe holds. Null means nobody has said.",
            "example": 4,
            "type": [
              "integer",
              "null"
            ]
          },
          "meterMode": {
            "type": "string",
            "description": "Which meters this airframe has. `none` (a glider, a balloon) means flights on it are NOT invoiced automatically: there is no meter reading to derive hours from, so the operator raises those invoices by hand. Ramp-out and ramp-in are unaffected.",
            "enum": [
              "hobbs_and_tach",
              "hobbs_only",
              "tach_only",
              "none"
            ]
          },
          "tachTime": {
            "type": "integer",
            "description": "Current tach reading."
          },
          "hobbsTime": {
            "type": "integer",
            "description": "Current Hobbs reading."
          },
          "rampedIn": {
            "type": "boolean",
            "description": "True when the aircraft is on the ramp and available."
          },
          "grounded": {
            "type": "boolean",
            "description": "True when the aircraft is unavailable for booking."
          },
          "groundedReason": {
            "description": "Why it is grounded.",
            "type": [
              "string",
              "null"
            ]
          },
          "fuelCapacity": {
            "type": "integer",
            "description": "Usable fuel capacity."
          },
          "fuelMeasurement": {
            "type": "string",
            "description": "Units for fuel figures.",
            "enum": [
              "gallons",
              "liters"
            ]
          },
          "fuelEngine1": {
            "type": "integer",
            "description": "Fuel on board, engine 1."
          },
          "fuelEngine2": {
            "description": "Fuel on board, engine 2, for twins.",
            "type": [
              "integer",
              "null"
            ]
          },
          "cost": {
            "type": "object",
            "description": "Billing rates, in cents.",
            "properties": {
              "dryRate": {
                "description": "Dry rate per hour, in cents.",
                "example": 16500,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "wetRate": {
                "description": "Wet rate per hour, in cents.",
                "example": 19500,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "billByHobbsTime": {
                "type": "boolean",
                "description": "Bill by Hobbs (true) or tach (false)."
              },
              "overnightMinimumTenths": {
                "description": "This aircraft's least billable time per night away, in TENTHS of an hour, applied as a floor on a booking that spans nights. `20` bills at least 2.0 hours a night.\n\n`null` means inherit the organization's figure; `0` means exempt this aircraft entirely. The two are not interchangeable, an exempt aircraft must stay exempt when the organization-wide figure changes.",
                "example": 20,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Resource": {
        "type": "object",
        "description": "Anything that can be booked: an aircraft, a simulator, or a room. The `type` object carries whichever detail applies.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "N12345"
          },
          "use": {
            "type": "string",
            "description": "AN AIRCRAFT IDEA. `fleet` is one of the organization's own: schedulable, and counted on its per-aircraft plan. `shop` is a customer's aircraft in for maintenance: it takes maintenance bookings only, it is never offered publicly, and it is not counted on the plan. Lists default to `fleet`; pass `scope=shop` or `scope=all` to see the rest. A room or a simulator always reads `fleet`, because the question does not apply to them, and writes that try to set `shop` on one are refused.",
            "example": "fleet"
          },
          "featuredImage": {
            "description": "URL of the resource photo.",
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "type": {
            "type": "object",
            "description": "Exactly one of these is populated.",
            "properties": {
              "aircraft": {
                "$ref": "#/components/schemas/Plane"
              },
              "simulator": {
                "type": "object",
                "description": "Simulator detail.",
                "properties": {},
                "additionalProperties": true
              },
              "room": {
                "type": "object",
                "description": "Room detail.",
                "properties": {
                  "roomNumber": {
                    "type": "string",
                    "description": "Room number."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "papers": {
            "type": "array",
            "description": "Booker-visible papers on this tail (POH, weight and balance). No keys. Staff-only files are omitted. Full rows with signed GET URLs are `GET /resources/{id}/files`.",
            "items": {
              "$ref": "#/components/schemas/ResourceFileSummary"
            }
          }
        },
        "additionalProperties": true
      },
      "ResourceFile": {
        "type": "object",
        "description": "One paper on a tail: a POH, weight and balance sheet, insurance cert, 337, logbook scan, or other PDF/photo. Not a member Document.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "category": {
            "type": "string",
            "description": "airworthiness_certificate, registration, poh, operating_limitations, weight_and_balance, insurance, form_337, logbook_scan, or other."
          },
          "visibility": {
            "type": "string",
            "description": "bookers (any member who can see the aircraft) or staff.",
            "enum": [
              "bookers",
              "staff"
            ]
          },
          "label": {
            "description": "Display name.",
            "type": [
              "string",
              "null"
            ]
          },
          "ownerVisible": {
            "type": "boolean",
            "description": "Shown to the aircraft's owners in their portal. Only ever true on a customer's aircraft (`use: shop`)."
          },
          "fileUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signed GET URLs. On the files list only, never on the resource summary."
          }
        },
        "additionalProperties": true
      },
      "ResourceFileSummary": {
        "type": "object",
        "description": "Enough to show a Papers tab without signing URLs. Booker-visible only when nested on a resource.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The paper id."
          },
          "category": {
            "type": "string",
            "description": "airworthiness_certificate, registration, poh, operating_limitations, weight_and_balance, insurance, form_337, logbook_scan, or other."
          },
          "label": {
            "description": "Display name.",
            "type": [
              "string",
              "null"
            ]
          },
          "hasFile": {
            "type": "boolean",
            "description": "True when a file was stored."
          }
        },
        "additionalProperties": true
      },
      "User": {
        "type": "object",
        "description": "A person. One account can belong to many organizations.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Full name."
          },
          "email": {
            "type": "string",
            "description": "Email address. Unique across the system.",
            "format": "email"
          },
          "phone": {
            "description": "Phone number.",
            "type": [
              "string",
              "null"
            ]
          },
          "profileImage": {
            "description": "URL of the profile photo.",
            "type": [
              "string",
              "null"
            ]
          },
          "emailVerifiedAt": {
            "format": "date-time",
            "description": "When the address was verified. Null means unverified.",
            "type": [
              "string",
              "null"
            ]
          },
          "details": {
            "$ref": "#/components/schemas/UserDetails"
          }
        },
        "additionalProperties": true
      },
      "UserDetails": {
        "type": "object",
        "description": "A person's contact record. Returned only to the person themselves, an admin or dispatcher of your organization, or an instructor the person is assigned to as a student, so expect it to be absent on most reads.",
        "properties": {
          "phone": {
            "description": "Primary, SMS-capable number, in **E.164** (`+13035551234`). Numbers sent to this API are normalized to E.164 on write and rejected if they are not real, dialable numbers; a number with no country code is read as US unless you send `defaultCountry`.",
            "example": "+13035551234",
            "type": [
              "string",
              "null"
            ]
          },
          "phoneCountry": {
            "description": "ISO 3166-1 alpha-2 country of `phone`, so it can be re-rendered nationally.",
            "example": "US",
            "type": [
              "string",
              "null"
            ]
          },
          "homePhone": {
            "description": "Home number, in E.164.",
            "type": [
              "string",
              "null"
            ]
          },
          "workPhone": {
            "description": "Work number, in E.164.",
            "type": [
              "string",
              "null"
            ]
          },
          "dateOfBirth": {
            "description": "Date of birth, `YYYY-MM-DD`. A calendar date, no time, no zone.",
            "format": "date",
            "example": "1994-03-17",
            "type": [
              "string",
              "null"
            ]
          },
          "preferredName": {
            "description": "What they go by, when that isn't their legal first name.",
            "type": [
              "string",
              "null"
            ]
          },
          "sex": {
            "description": "Free text.",
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": "object",
            "description": "A postal address.",
            "properties": {
              "streetAddress1": {
                "type": "string",
                "description": "Street address."
              },
              "streetAddress2": {
                "description": "Suite, unit, or hangar number.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": "string",
                "description": "City."
              },
              "state": {
                "type": "string",
                "description": "State or region."
              },
              "zipCode": {
                "type": "string",
                "description": "Postal code."
              },
              "country": {
                "type": "string",
                "description": "Country."
              },
              "coordinates": {
                "type": "object",
                "description": "Geocoded position, when the address could be resolved.",
                "properties": {
                  "lat": {
                    "type": "number",
                    "description": "Latitude."
                  },
                  "lng": {
                    "type": "number",
                    "description": "Longitude."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "emergencyContacts": {
            "type": "array",
            "description": "Everyone on this person's emergency list, primary first.",
            "items": {
              "$ref": "#/components/schemas/EmergencyContact"
            }
          }
        },
        "additionalProperties": true
      },
      "EmergencyContact": {
        "type": "object",
        "description": "Who to call about a person in an emergency.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "The contact's full name.",
            "example": "Dana Reyes"
          },
          "relationship": {
            "description": "How they are related. Free text.",
            "example": "Spouse",
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": "string",
            "description": "Primary number, in E.164.",
            "example": "+13035551234"
          },
          "altPhone": {
            "description": "Second number to try, in E.164.",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "description": "Email address.",
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "description": "Anything else worth knowing when calling.",
            "maxLength": 300,
            "type": [
              "string",
              "null"
            ]
          },
          "isPrimary": {
            "type": "boolean",
            "description": "True for the one to try first. At most one per person."
          },
          "sortOrder": {
            "type": "integer",
            "description": "Display order within the person's list."
          }
        },
        "additionalProperties": true
      },
      "OrganizationUser": {
        "type": "object",
        "description": "A person's membership of an organization. This (not `User`) is what schedules, invoices, and permissions hang off, because one person can belong to several organizations with different roles in each.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "roles": {
            "type": "array",
            "description": "Roles held in this organization. Additive and non-hierarchical: a person can be both instructor and technician.",
            "items": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "dispatcher",
                "instructor",
                "student",
                "renter",
                "technician"
              ]
            }
          },
          "archivedAt": {
            "description": "When the organization retired this person from its roster; null for a current member. Archived members receive no notifications of any kind, are omitted from member lists unless asked for, and cannot be booked. Distinct from `grounded`, which is a live restriction on somebody who is still here.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "approvedForResource": {
            "type": "boolean",
            "description": "Whether this member is checked out on the aircraft named by the request's `approvedForResourceId`. Present only when that filter was passed; absent otherwise."
          },
          "external": {
            "type": "boolean",
            "description": "True for an outside party rather than one of the organization's own people: an aircraft owner whose aeroplane the maintenance shop is looking after. They are on the roster so the work can be invoiced to them, they hold no flying role, and almost every route refuses them. False for every member."
          },
          "claimedAt": {
            "description": "When this person confirmed the membership themselves, by accepting an invitation or entering a join code. Null means the organization typed them in and nobody has confirmed: they cannot sign in and receive no notifications. Every membership created the ordinary way is claimed the moment it exists.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "contactEmail": {
            "description": "The address the organization uses to reach an outside party (`external: true`) who has not signed up: an aircraft owner's real email, kept here because their login is a placeholder. Null for every member, and cleared once the person claims the membership, when their own login address takes over. Present on the single-member read.",
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Reservation": {
        "type": "object",
        "description": "A booking of a resource, a person, or both.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "title": {
            "type": "string",
            "description": "Short label shown on the calendar.",
            "maxLength": 50
          },
          "type": {
            "type": "string",
            "description": "What kind of booking this is. Which types a caller may CREATE depends on their roles, see the Reservation types section.",
            "enum": [
              "solo",
              "shared",
              "dual",
              "ground",
              "guest",
              "sim",
              "rental",
              "maintenance",
              "other"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Start instant. MUST carry an explicit offset or `Z`."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "End instant. MUST carry an explicit offset or `Z`.\n\nMAY fall on a later calendar day than `start`. An organization with multi-day bookings enabled can book a trip that keeps the resource for the whole span, and the resource is unavailable for every day in between rather than becoming free again each morning. Do not assume a booking ends on the day it starts."
          },
          "timeZoneName": {
            "type": "string",
            "description": "IANA zone the booking is anchored to, e.g. `America/Denver`. Resolved from the LOCATION, falling back to the organization, not from the caller's device, because how many nights a trip spans decides what it bills.",
            "example": "America/Denver"
          },
          "notes": {
            "description": "Free-text notes.",
            "maxLength": 200,
            "type": [
              "string",
              "null"
            ]
          },
          "cancelledAt": {
            "format": "date-time",
            "description": "When it was cancelled. Null for a live booking.",
            "type": [
              "string",
              "null"
            ]
          },
          "cancellationReason": {
            "description": "Free-text detail given at cancellation.",
            "maxLength": 200,
            "type": [
              "string",
              "null"
            ]
          },
          "cancellationCategory": {
            "description": "Fixed-list reason. Null on bookings cancelled before this was introduced, and on those the system cancelled for you, reports count those as “Not recorded”, not as a category somebody chose. May also read back a RETIRED value such as `other`, which is no longer accepted on write but is still reported on.",
            "type": [
              "string",
              "null"
            ]
          },
          "rrule": {
            "description": "Recurrence rule, when this booking belongs to a repeating series.",
            "type": [
              "string",
              "null"
            ]
          },
          "resource": {
            "$ref": "#/components/schemas/Resource"
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "personnel": {
            "type": "object",
            "description": "Who is on the booking.",
            "properties": {
              "instructors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OrganizationUser"
                }
              },
              "students": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OrganizationUser"
                }
              },
              "renters": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/OrganizationUser"
                }
              },
              "guests": {
                "type": "array",
                "items": {
                  "type": "object",
                  "description": "A non-member on the booking.",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "Name."
                    },
                    "email": {
                      "type": "string",
                      "description": "Email."
                    },
                    "phone": {
                      "type": "string",
                      "description": "Phone."
                    }
                  },
                  "additionalProperties": true
                }
              }
            },
            "additionalProperties": true
          },
          "collectionStyle": {
            "type": "string",
            "description": "When money is collected. `close_out` (default) prices Hobbs after the flight. `prepaid_fixed` invoices a package amount when the reservation is created. Public offerings copy this onto the booking at approval. Not a public-calendar-only checkout.",
            "enum": [
              "close_out",
              "prepaid_fixed"
            ]
          },
          "prepaidAmountCents": {
            "description": "Package price in cents when collectionStyle is prepaid_fixed.",
            "type": [
              "integer",
              "null"
            ]
          },
          "prepaidInvoice": {
            "$ref": "#/components/schemas/Invoice",
            "description": "The package invoice when collectionStyle is prepaid_fixed. Kept off `invoices[]` so ramp-out and close-out still run."
          },
          "invoices": {
            "type": "array",
            "description": "ONE PER PAYER. A booking shared between several people mints an invoice each, a Stripe invoice bills exactly one customer. A booking with one payer has an array of one, and an un-billed booking has an empty array.\n\nDo not read `invoices[0]` as \"the invoice\": on a split booking that is one person's share. Ask whether EVERY payer has one.\n\nPackage invoices from prepaid_fixed are not in this array; they are `prepaidInvoice`.\n\nAmounts, pay links, and rate overrides follow GET /invoices/{id}: a member only receives invoices billed to them (and a guest-flight instructor or creator can receive that guest package). Desk (owner, admin, dispatcher) receives every invoice on a booking they can open.",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          },
          "invoice": {
            "$ref": "#/components/schemas/Invoice",
            "description": "**Deprecated, use `invoices`.** The first non-void invoice, kept so clients written before split billing keep working. On a booking with one payer this is exactly what it always meant; on a split booking it is only ONE person's share, so anything that reports whether a flight has been billed should read `invoices`. This field will be removed."
          },
          "review": {
            "type": "object",
            "description": "Close-out figures, populated once the flight is ramped out and back in.",
            "properties": {
              "hobbsTimeOut": {
                "description": "Hobbs at departure.",
                "type": [
                  "number",
                  "null"
                ]
              },
              "hobbsTimeIn": {
                "description": "Hobbs at return.",
                "type": [
                  "number",
                  "null"
                ]
              },
              "tachTimeOut": {
                "description": "Tach at departure.",
                "type": [
                  "number",
                  "null"
                ]
              },
              "tachTimeIn": {
                "description": "Tach at return.",
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Squawk": {
        "type": "object",
        "description": "A reported aircraft discrepancy.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "title": {
            "type": "string",
            "description": "Short summary.",
            "example": "Nav light flickering"
          },
          "description": {
            "type": "string",
            "description": "Full description of the discrepancy."
          },
          "priority": {
            "type": "string",
            "description": "How urgent it is. Set when the squawk is filed; `normal` if nothing was given.",
            "enum": [
              "grounding",
              "high",
              "normal",
              "low"
            ],
            "example": "normal"
          },
          "grounding": {
            "type": "boolean",
            "description": "Convenience for `priority == \"grounding\"`. Records the reporter's judgement that the aircraft should not fly. It does NOT set the aircraft's `grounded` flag, which is an administrator action on the resource itself."
          },
          "reportedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was reported."
          },
          "resolvedAt": {
            "format": "date-time",
            "description": "When it was cleared. Null while open.",
            "type": [
              "string",
              "null"
            ]
          },
          "resource": {
            "$ref": "#/components/schemas/Resource"
          },
          "fileUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signed GET URLs for photos and PDFs attached when the squawk was filed. On the single-squawk read only. List rows send `hasAttachments` instead of keys."
          },
          "hasAttachments": {
            "type": "boolean",
            "description": "True when this squawk or any of its notes has a photo or PDF. Always present on list rows so a paperclip can render without signing N files."
          },
          "comments": {
            "type": "array",
            "description": "The running thread, oldest first. Returned by the single-squawk read only, never on a list row.",
            "items": {
              "$ref": "#/components/schemas/SquawkComment"
            }
          }
        },
        "additionalProperties": true
      },
      "SquawkComment": {
        "type": "object",
        "description": "One note on a squawk. Append-only: there is no edit and no delete, and a mistake is corrected by adding another note.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The note id."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was written."
          },
          "body": {
            "type": "string",
            "description": "The note. Empty when the note is photos or a PDF only.",
            "example": "Ordered the pump, ETA Friday. Aircraft stays on the line."
          },
          "fileUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signed GET URLs for photos and PDFs on this note. On the single-squawk read only."
          },
          "author": {
            "$ref": "#/components/schemas/OrganizationUser",
            "description": "Who wrote it. Null where that person has since left the organization: the note stays, because the account of what was done to an aircraft outlives anyone's membership."
          }
        },
        "additionalProperties": true
      },
      "PresignedPost": {
        "type": "object",
        "description": "An S3 presigned POST the client uploads one file to. Append every field first and the file part last.",
        "properties": {
          "url": {
            "type": "string",
            "description": "The S3 POST URL."
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Form fields S3 requires. The `key` is the object path that was stored on the row."
          },
          "maxBytes": {
            "type": "integer",
            "description": "Size ceiling this POST will accept, in bytes. 25 MB for squawk photos and PDFs."
          }
        },
        "additionalProperties": true
      },
      "MaintenanceDue": {
        "type": "object",
        "description": "How much is left on a reminder, worked out from its template's interval and the aircraft's current meters. Computed on read, it is not stored.",
        "properties": {
          "kind": {
            "type": "string",
            "description": "What the interval is counted in.",
            "enum": [
              "hours",
              "days",
              "date",
              "unknown"
            ]
          },
          "status": {
            "type": "string",
            "description": "Which band it is in. `dueSoon` is the template's own warning period, the same threshold that sends the warning notification. `notApplicable`: marked as not applying to this aircraft, so it is never overdue or due soon whatever its clocks say (the figures below are still computed).",
            "enum": [
              "overdue",
              "dueSoon",
              "ok",
              "resolved",
              "notApplicable"
            ]
          },
          "name": {
            "description": "The template's name, lifted so a row can be rendered without it.",
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "description": "The template's notes.",
            "type": [
              "string",
              "null"
            ]
          },
          "grounds": {
            "type": "boolean",
            "description": "Whether coming due takes the aircraft off the line."
          },
          "dueAt": {
            "format": "date-time",
            "description": "When it comes due. Null on a meter-based interval, nobody can know the date.",
            "type": [
              "string",
              "null"
            ]
          },
          "daysRemaining": {
            "description": "Whole days until `dueAt`. Negative means overdue.",
            "type": [
              "integer",
              "null"
            ]
          },
          "basis": {
            "description": "Which meter a meter-based interval counts.",
            "enum": [
              "tach",
              "hobbs",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "dueAtHours": {
            "description": "Meter reading it comes due at, in tenths of an hour.",
            "example": 3000,
            "type": [
              "integer",
              "null"
            ]
          },
          "hoursRemaining": {
            "description": "Tenths of an hour left on that meter. Negative means overdue.",
            "example": 124,
            "type": [
              "integer",
              "null"
            ]
          },
          "currentHours": {
            "description": "What the meter reads now, in tenths of an hour.",
            "type": [
              "integer",
              "null"
            ]
          },
          "progress": {
            "description": "0 is a fresh interval, 1 is due now, above 1 is overdue.",
            "type": [
              "number",
              "null"
            ]
          },
          "urgency": {
            "type": "number",
            "description": "Sort key, ascending is most urgent first, comparable across all three kinds."
          },
          "combined": {
            "type": "boolean",
            "description": "Whether the template counts an hour clock AND a calendar clock, `whichever comes first`."
          },
          "also": {
            "$ref": "#/components/schemas/MaintenanceDueSide",
            "description": "On a combined interval, the clock that did NOT come first. The fields above always describe the one that did."
          }
        },
        "additionalProperties": true
      },
      "MaintenanceDueSide": {
        "type": "object",
        "description": "One clock of a combined interval. Same figures as `MaintenanceDue`, for the side that is not the binding one.",
        "properties": {
          "kind": {
            "type": "string",
            "description": "What this clock is counted in.",
            "enum": [
              "hours",
              "days"
            ]
          },
          "status": {
            "type": "string",
            "description": "Which band this clock is in.",
            "enum": [
              "overdue",
              "dueSoon",
              "ok",
              "resolved"
            ]
          },
          "dueAt": {
            "format": "date-time",
            "description": "When this clock comes due. Null on the meter side.",
            "type": [
              "string",
              "null"
            ]
          },
          "daysRemaining": {
            "description": "Whole days until `dueAt`. Negative means overdue.",
            "type": [
              "integer",
              "null"
            ]
          },
          "basis": {
            "description": "Which meter this clock counts.",
            "enum": [
              "tach",
              "hobbs",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "dueAtHours": {
            "description": "Meter reading this clock comes due at, in tenths of an hour.",
            "type": [
              "integer",
              "null"
            ]
          },
          "hoursRemaining": {
            "description": "Tenths of an hour left. Negative means overdue.",
            "type": [
              "integer",
              "null"
            ]
          },
          "currentHours": {
            "description": "What the meter reads now, in tenths of an hour.",
            "type": [
              "integer",
              "null"
            ]
          },
          "progress": {
            "description": "0 is a fresh interval, 1 is due now, above 1 is overdue.",
            "type": [
              "number",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "MaintenanceReminder": {
        "type": "object",
        "description": "A due-date or due-hours reminder against one aircraft.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "startedAt": {
            "format": "date-time",
            "description": "When the current interval began.",
            "type": [
              "string",
              "null"
            ]
          },
          "startHours": {
            "description": "Meter reading the interval started at.",
            "type": [
              "integer",
              "null"
            ]
          },
          "completedAt": {
            "format": "date-time",
            "description": "When the work was signed off.",
            "type": [
              "string",
              "null"
            ]
          },
          "due": {
            "$ref": "#/components/schemas/MaintenanceDue"
          },
          "steps": {
            "type": "object",
            "description": "The reminder steps that apply on this aircraft: its own, else the rule's, else the defaults. Days; hours in TENTHS.",
            "properties": {
              "shopDays": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "shopHours": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "ownerDays": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "ownerHours": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "additionalProperties": true
          },
          "ownSteps": {
            "description": "The steps this aircraft sets itself, key by key. Null when it follows the rule.",
            "properties": {},
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "resource": {
            "$ref": "#/components/schemas/Resource"
          },
          "hasAttachments": {
            "type": "boolean",
            "description": "Whether the open inspection has working files. List rows send this instead of keys."
          },
          "fileUrls": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Signed GET URLs for photos and PDFs on the open inspection. On the single-reminder read only. List rows send `hasAttachments` instead of keys. Copied onto the compliance record at sign-off."
          },
          "resolvedBy": {
            "description": "Who signed it off. Null while it is open.",
            "type": [
              "object",
              "null"
            ]
          },
          "workOrderItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The work order items carrying this inspection, each with its work order's id, number and status. On the single-reminder read only, and only for admins and technicians."
          },
          "work": {
            "description": "Whether an open work order has this inspection booked in: `scheduled` while the job is requested or scheduled, `inProgress` while the aircraft is at the shop. Derived from the job, never stored. Null when no open job carries it (an item the owner declined or put off does not count), and always null for a caller who may not open work orders (a dispatcher).",
            "properties": {
              "status": {
                "type": "string",
                "description": "Where the job stands.",
                "enum": [
                  "scheduled",
                  "inProgress"
                ]
              },
              "job": {
                "type": "object",
                "description": "The job carrying it.",
                "properties": {
                  "id": {
                    "type": "integer",
                    "description": "The work order."
                  },
                  "label": {
                    "type": "string",
                    "description": "The job's number as shown.",
                    "example": "WO-1042"
                  },
                  "status": {
                    "type": "string",
                    "description": "The work order's stage."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "notApplicableAt": {
            "format": "date-time",
            "description": "When this inspection was marked not applicable on this aircraft. While set, it is never warned, reminded or grounded, and `due.status` reads `notApplicable`. Carried to the next cycle at sign-off.",
            "type": [
              "string",
              "null"
            ]
          },
          "notApplicableReason": {
            "description": "Why it does not apply, as the shop wrote it (for example, VFR only).",
            "type": [
              "string",
              "null"
            ]
          },
          "notApplicableBy": {
            "description": "Who marked it not applicable. On the single-reminder read only.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "The member."
              },
              "user": {
                "type": "object",
                "description": "Their account.",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Their name."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "MaintenanceReminderTemplate": {
        "type": "object",
        "description": "The rule a maintenance reminder repeats on.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "100 Hour Inspection"
          },
          "notes": {
            "description": "Free-text notes.",
            "type": [
              "string",
              "null"
            ]
          },
          "repeat": {
            "type": "boolean",
            "description": "Whether a new interval starts when this one is signed off."
          },
          "ground": {
            "type": "boolean",
            "description": "Whether the aircraft is grounded when this comes due."
          },
          "remindDays": {
            "description": "Interval in days, for calendar-based reminders. May be set alongside `remindHours` for `whichever comes first`. When `remindMonths` is set this is a DERIVED approximation kept for older clients and is not what the due date is computed from.",
            "example": 365,
            "type": [
              "integer",
              "null"
            ]
          },
          "remindMonths": {
            "description": "Interval in CALENDAR MONTHS, which is what 14 CFR 91.409(a), 91.411, 91.413 and 91.207(d) actually require. A calendar month runs to the end of the month: an annual signed on any day in February is due at the end of February the following year. Wins over `remindDays`, which the server derives from this on write. Read on the organization's own time zone.",
            "example": 12,
            "type": [
              "integer",
              "null"
            ]
          },
          "remindDaysBefore": {
            "description": "How many days ahead to warn.",
            "type": [
              "integer",
              "null"
            ]
          },
          "remindHours": {
            "description": "Interval in TENTHS OF AN HOUR, for meter-based reminders. `1000` is a 100-hour inspection. Every hour field in this API is tenths; sending `100` here schedules a 10-hour interval. May be set alongside `remindDays` for `whichever comes first`.",
            "example": 1000,
            "type": [
              "integer",
              "null"
            ]
          },
          "remindHoursBefore": {
            "description": "How many tenths of an hour ahead to warn. `100` warns 10 hours out.",
            "example": 100,
            "type": [
              "integer",
              "null"
            ]
          },
          "reminderSteps": {
            "description": "The rule's own reminder steps (`shopDays`, `shopHours`, `ownerDays`, `ownerHours`); a key not set is the default. See `PATCH /maintenance/reminders/templates/{id}`.",
            "properties": {},
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "hourBasedOn": {
            "description": "Which meter drives it.",
            "enum": [
              "tach",
              "hobbs",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "remindDate": {
            "format": "date-time",
            "description": "A one-off due date. Exclusive: a dated reminder does not also repeat.",
            "type": [
              "string",
              "null"
            ]
          },
          "remindAtHours": {
            "description": "A ONE-OFF deadline at an absolute meter reading, in tenths of an hour: `12500` is due when the meter reads 1250.0. This is how a great many Airworthiness Directives are actually written (\"comply within the next 50 hours\"). Exclusive with `remindHours`, which is an interval, and with the calendar fields. Does not repeat.",
            "example": 12500,
            "type": [
              "integer",
              "null"
            ]
          },
          "sourceType": {
            "description": "What kind of rule this is. `ad` is an Airworthiness Directive, binding under 14 CFR Part 39; `sb` is an advisory Service Bulletin.",
            "enum": [
              "ad",
              "sb",
              "manufacturer",
              "shop",
              "other",
              null
            ],
            "example": "ad",
            "type": [
              "string",
              "null"
            ]
          },
          "sourceRef": {
            "description": "The document number. Required when `sourceType` is `ad`.",
            "example": "2015-19-07",
            "type": [
              "string",
              "null"
            ]
          },
          "sourceUrl": {
            "description": "A link to the issuing authority's copy. Must be http or https.",
            "type": [
              "string",
              "null"
            ]
          },
          "revision": {
            "description": "The revision currently in force. The revision actually complied with is snapshotted onto each compliance record.",
            "example": "2",
            "type": [
              "string",
              "null"
            ]
          },
          "componentClock": {
            "description": "Set on an inspection a life-limited component keeps (`/resources/{id}/components`): `hours` when it tracks the component's hour limit (a one-off `remindAtHours`), `date` for its calendar limit (a one-off `remindDate`). Its due point, aircraft and clock are the component's: change them there. Deleting it, or changing its aircraft, is refused with 400. Null on every other inspection.",
            "enum": [
              "hours",
              "date",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "MaintenanceComplianceRecord": {
        "type": "object",
        "description": "A signed-off inspection, kept permanently. Append-only: there is no endpoint that updates or deletes one, and the fields describing which rule was complied with are frozen at signature rather than read back through the template, so a superseded Airworthiness Directive cannot rewrite history. Written by signing off a reminder.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifier."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was written."
          },
          "sourceType": {
            "description": "Copied from the template at signature.",
            "enum": [
              "ad",
              "sb",
              "manufacturer",
              "shop",
              "other",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "sourceRef": {
            "description": "The document number as it stood at signature.",
            "example": "2015-19-07",
            "type": [
              "string",
              "null"
            ]
          },
          "revision": {
            "description": "The revision as it stood at signature.",
            "example": "2",
            "type": [
              "string",
              "null"
            ]
          },
          "templateName": {
            "type": "string",
            "description": "The rule's name at signature.",
            "example": "AD 2015-19-07 Wing spar"
          },
          "complianceDate": {
            "type": "string",
            "format": "date-time",
            "description": "When the work was completed."
          },
          "tachAtCompliance": {
            "description": "Tach reading at compliance, in tenths of an hour. NOT time in service as 14 CFR 1.1 defines it.",
            "example": 11020,
            "type": [
              "integer",
              "null"
            ]
          },
          "hobbsAtCompliance": {
            "description": "Hobbs reading at compliance, in tenths of an hour. NOT time in service as 14 CFR 1.1 defines it.",
            "example": 11255,
            "type": [
              "integer",
              "null"
            ]
          },
          "nextDueAt": {
            "format": "date-time",
            "description": "When the next action falls due, frozen at signature.",
            "type": [
              "string",
              "null"
            ]
          },
          "nextDueAtHours": {
            "description": "Meter reading the next action falls due at, in tenths of an hour.",
            "type": [
              "integer",
              "null"
            ]
          },
          "mechanicName": {
            "type": "string",
            "description": "Who certified the work. Stored as text, because an outside inspector has no account here.",
            "example": "Dale Whitfield"
          },
          "mechanicCertificateNumber": {
            "description": "Their FAA certificate number.",
            "example": "3421887",
            "type": [
              "string",
              "null"
            ]
          },
          "mechanicCertificateType": {
            "description": "A&P, IA, or repair station.",
            "example": "IA",
            "type": [
              "string",
              "null"
            ]
          },
          "methodOfCompliance": {
            "type": "string",
            "description": "What was actually done, as required by 14 CFR 91.417."
          },
          "fileUrls": {
            "type": "array",
            "description": "Attached evidence, for example a photograph of the logbook entry.",
            "items": {
              "type": "string"
            }
          },
          "reminderId": {
            "type": "integer",
            "description": "The inspection (reminder) this record signed off. On list rows."
          }
        },
        "additionalProperties": true
      },
      "InspectionPreset": {
        "type": "object",
        "description": "A ready-made inspection interval. Reference data: POST its `payload` to `/maintenance/reminders/templates` to actually create one.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier for the preset.",
            "example": "hundred-hour"
          },
          "letter": {
            "description": "Which letter of the AVIATES mnemonic this covers. Null for the presets outside it.",
            "example": "1",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "100-hour inspection"
          },
          "regulation": {
            "description": "The rule it comes from.",
            "example": "§91.409(b)",
            "type": [
              "string",
              "null"
            ]
          },
          "interval": {
            "type": "string",
            "description": "The default interval, in words.",
            "example": "Every 100 hours on the tach, warn 10 hours out"
          },
          "caveat": {
            "description": "Where the default does not apply to every aircraft.",
            "type": [
              "string",
              "null"
            ]
          },
          "ground": {
            "type": "boolean",
            "description": "Whether the default grounds the aircraft when it comes due."
          },
          "payload": {
            "type": "object",
            "description": "Exactly the body `POST /maintenance/reminders/templates` accepts.",
            "properties": {},
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Invoice": {
        "type": "object",
        "description": "A bill: for a completed reservation, or raised by hand (`POST /invoices`). In ledger mode, member close-outs and desk charges may return an invoice-shaped stub instead of a Stripe invoice row.\n\n`subtotal` is the sum of `qty × unitPrice` over the lines; `total` is what the customer is charged, which is `subtotal + tax`.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "paidAt": {
            "format": "date-time",
            "description": "When it was paid. Null while outstanding.",
            "type": [
              "string",
              "null"
            ]
          },
          "dueAt": {
            "format": "date-time",
            "description": "When payment is due.",
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "description": "Note shown on the invoice.",
            "type": [
              "string",
              "null"
            ]
          },
          "purpose": {
            "type": "string",
            "description": "What kind of bill: `flight`, `prepaid_package`, `enrollment`, `membership` or `work_order`."
          },
          "workOrder": {
            "description": "The shop job a work order bill was raised for.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Work order id."
              },
              "number": {
                "type": "integer",
                "description": "Its number (WO-1042 is 1042)."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "subtotal": {
            "description": "Sum of the lines in cents, before sales tax.",
            "type": [
              "integer",
              "null"
            ]
          },
          "tax": {
            "description": "Sales tax in cents: the sum of the lines' `taxCents`. Null or 0 when none.",
            "type": [
              "integer",
              "null"
            ]
          },
          "total": {
            "description": "What the customer is charged, in cents: subtotal plus tax.",
            "type": [
              "integer",
              "null"
            ]
          },
          "taxExemptNote": {
            "description": "Why this invoice carries no sales tax, printed on it, e.g. `Tax exempt: Resale (ST-101 on file)`. Null when the customer is not exempt.",
            "type": [
              "string",
              "null"
            ]
          },
          "paymentMethod": {
            "description": "How a payment recorded outside the card flow came in: `cash`, `check`, `card_in_person`, `bank_transfer` or `other` (recorded with Mark paid), or `manual` (marked paid in the organization's own Stripe dashboard). Null for a card or bank payment through Stripe, and for a payment marked at the desk before methods were recorded (2026-10-05).",
            "type": [
              "string",
              "null"
            ]
          },
          "checkNumber": {
            "description": "The check's number, when it was paid by check and somebody wrote it down.",
            "example": "1234",
            "type": [
              "string",
              "null"
            ]
          },
          "paymentReceivedOn": {
            "description": "The day the organization says the money arrived, for a payment recorded with Mark paid. A calendar day: midnight UTC, so read the first ten characters (`2026-10-03`) and never shift it into a zone.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "paymentNote": {
            "description": "The organization's note about the payment, e.g. `Deposited with the Oct 4 batch`. Not shown to an aircraft owner from outside the organization.",
            "type": [
              "string",
              "null"
            ]
          },
          "billTo": {
            "description": "Who the bill is to, as it prints: on `GET /invoices/{invoiceId}` for the person billed and for administrators only. The billing address is the one the organization keeps for a shop customer; the phone is stored E.164 with its country.",
            "properties": {
              "name": {
                "description": "Their name.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "description": "Where the bill goes: the address on file for an owner the organization recorded, otherwise their sign-in address.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "description": "Their phone, E.164.",
                "example": "+12085550141",
                "type": [
                  "string",
                  "null"
                ]
              },
              "phoneCountry": {
                "description": "ISO 3166-1 alpha-2 of the phone, for printing it nationally.",
                "example": "US",
                "type": [
                  "string",
                  "null"
                ]
              },
              "billingAddress": {
                "description": "The billing address as written, one line per line.",
                "example": "Whitcomb Aviation LLC\n12 Taxiway B\nIdaho Falls, ID 83402",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "description": "Line items.",
            "items": {
              "$ref": "#/components/schemas/InvoiceItem"
            }
          },
          "orgUser": {
            "$ref": "#/components/schemas/OrganizationUser"
          },
          "reservation": {
            "$ref": "#/components/schemas/Reservation"
          },
          "isLedgerCharge": {
            "type": "boolean",
            "description": "True when this payload is a ledger charge stub (flight_charge / item_charge / fee), not a Stripe Invoice row. Clients must not call retrieveInvoice on these ids."
          },
          "ledgerEntryId": {
            "description": "Ledger entry id when `isLedgerCharge` is true. Same as `id` on the stub.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "InvoiceItem": {
        "type": "object",
        "description": "One line on an invoice. The line's sales tax is frozen when the invoice is raised: it is Stripe's figure, and the rate is copied onto the line, so changing a rate later never rewrites it.",
        "properties": {
          "name": {
            "type": "string",
            "description": "What it is for.",
            "maxLength": 200
          },
          "qty": {
            "type": "integer",
            "description": "Quantity, a whole number."
          },
          "unitPrice": {
            "type": "integer",
            "description": "Unit price in cents, before tax."
          },
          "category": {
            "description": "What the line is: `labor`, `part`, `supply`, `outside_service`, `freight`, `fee`, `rental`, `instruction` or `other`, as the sender said. Decides whether it is taxed by default. Null when nobody said: lines written before 2026-09-29, lines close-out mints, lines from a client that sends no category, and the service fee line on a bill whose other lines said nothing about themselves.",
            "type": [
              "string",
              "null"
            ]
          },
          "taxable": {
            "type": "boolean",
            "description": "Whether sales tax was charged on this line."
          },
          "taxCents": {
            "type": "integer",
            "description": "The line's whole sales tax in cents. Zero when not taxable."
          },
          "taxRateName": {
            "description": "The rate's name when it was charged, e.g. `Idaho sales tax`.",
            "type": [
              "string",
              "null"
            ]
          },
          "taxRatePpm": {
            "description": "The rate when it was charged. Parts per million of the amount taxed: 60000 is 6%, 81875 is 8.1875%.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "InvoicePreview": {
        "type": "object",
        "description": "A bill priced but not sent: what `POST /invoices` would raise for the same body.",
        "properties": {
          "lines": {
            "type": "array",
            "description": "Every line, including the organization's service fee when it charges one.",
            "items": {
              "type": "object",
              "description": "A priced line.",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "What it is for."
                },
                "qty": {
                  "type": "integer",
                  "description": "Quantity."
                },
                "unitPrice": {
                  "type": "integer",
                  "description": "Unit price in cents."
                },
                "category": {
                  "type": "string",
                  "description": "What the line is."
                },
                "taxable": {
                  "type": "boolean",
                  "description": "Whether sales tax applies."
                },
                "taxRateName": {
                  "description": "The rate that applies.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "taxRatePpm": {
                  "description": "That rate. Parts per million of the amount taxed: 60000 is 6%, 81875 is 8.1875%.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "taxCents": {
                  "type": "integer",
                  "description": "Sales tax on this line in cents."
                },
                "isServiceFee": {
                  "type": "boolean",
                  "description": "True for the organization's own service fee line."
                }
              },
              "additionalProperties": true
            }
          },
          "byRate": {
            "type": "array",
            "description": "Tax per rate: the base it was charged on and the tax.",
            "items": {
              "type": "object",
              "description": "One rate.",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "The rate's name."
                },
                "ratePpm": {
                  "type": "integer",
                  "description": "Parts per million of the amount taxed: 60000 is 6%, 81875 is 8.1875%."
                },
                "base": {
                  "type": "integer",
                  "description": "Taxable amount in cents."
                },
                "tax": {
                  "type": "integer",
                  "description": "Tax in cents."
                }
              },
              "additionalProperties": true
            }
          },
          "subtotal": {
            "type": "integer",
            "description": "Sum of the lines in cents."
          },
          "tax": {
            "type": "integer",
            "description": "Sales tax in cents."
          },
          "total": {
            "type": "integer",
            "description": "Subtotal plus tax."
          },
          "salesTaxConfigured": {
            "type": "boolean",
            "description": "Whether the organization charges sales tax at all."
          },
          "refusal": {
            "description": "Why POST /invoices would refuse this bill as it stands (a taxed charge to a member's account balance), or null. The lines are priced either way.",
            "type": [
              "string",
              "null"
            ]
          },
          "exemption": {
            "description": "The customer's exemption, when they have one.",
            "properties": {
              "reason": {
                "type": "string",
                "description": "The reason code."
              },
              "label": {
                "type": "string",
                "description": "The reason in words."
              },
              "note": {
                "description": "The organization's note, e.g. which certificate it holds.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "printed": {
                "type": "string",
                "description": "What the invoice will say."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "SalesTaxRate": {
        "type": "object",
        "description": "A sales tax rate an organization charges. Once a line has been charged at it, only its name can change: a new percentage, country or state means archiving it and adding another.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Rate id."
          },
          "name": {
            "type": "string",
            "description": "Printed on invoices beside the tax, e.g. `Idaho sales tax`.",
            "maxLength": 50
          },
          "ratePpm": {
            "type": "integer",
            "description": "Parts per million of the amount taxed: 60000 is 6%, 81875 is 8.1875%."
          },
          "country": {
            "type": "string",
            "description": "Where the tax is owed, as an ISO 3166-1 alpha-2 code. `US` unless the rate is charged abroad."
          },
          "jurisdiction": {
            "description": "In the US, the state's two-letter code. Abroad, a region as the organization wants it printed. Printed beside the rate. Optional.",
            "type": [
              "string",
              "null"
            ]
          },
          "archivedAt": {
            "format": "date-time",
            "description": "When it was archived. Archived rates never apply.",
            "type": [
              "string",
              "null"
            ]
          },
          "usedOnLines": {
            "type": "integer",
            "description": "Invoice lines ever charged at this rate. Its percentage, country and state cannot change once this is above zero."
          }
        },
        "additionalProperties": true
      },
      "SalesTaxSettings": {
        "type": "object",
        "description": "An organization's sales tax setup.",
        "properties": {
          "states": {
            "type": "array",
            "description": "Every US state and DC with its statewide rate (the floor before city or county taxes), for picking a state. See statewideRatesAsOf.",
            "items": {
              "type": "object",
              "description": "A state.",
              "properties": {
                "code": {
                  "type": "string",
                  "description": "Two-letter code."
                },
                "name": {
                  "type": "string",
                  "description": "Name."
                },
                "ratePpm": {
                  "type": "integer",
                  "description": "Statewide rate in parts per million: 60000 is 6%. 0 where there is no statewide sales tax."
                }
              },
              "additionalProperties": true
            }
          },
          "countries": {
            "type": "array",
            "description": "Every other country, by name, for a rate charged outside the US.",
            "items": {
              "type": "object",
              "description": "A country.",
              "properties": {
                "code": {
                  "type": "string",
                  "description": "ISO 3166-1 alpha-2 code."
                },
                "name": {
                  "type": "string",
                  "description": "Name."
                }
              },
              "additionalProperties": true
            }
          },
          "statewideRatesAsOf": {
            "type": "string",
            "description": "The date the statewide rates are from."
          },
          "statewideRatesSource": {
            "type": "string",
            "description": "Where the statewide rates come from."
          },
          "outsideUs": {
            "type": "boolean",
            "description": "Every airport with an address is outside the US, so a new rate starts on a country rather than a state."
          },
          "suggestion": {
            "description": "The state all of the organization's airports with an address are in, when there is exactly one.",
            "properties": {
              "state": {
                "type": "object",
                "description": "The state.",
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "Code."
                  },
                  "name": {
                    "type": "string",
                    "description": "Name."
                  },
                  "ratePpm": {
                    "type": "integer",
                    "description": "Statewide rate."
                  }
                },
                "additionalProperties": true
              },
              "from": {
                "type": "string",
                "description": "Where it was read from, e.g. `your airport in Preston, Idaho` or `your 2 locations in Arizona`."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "rates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesTaxRate"
            }
          },
          "rules": {
            "type": "object",
            "description": "Which categories of line are taxed by default, and at which rate: `{ category: rateId }`. A category that is absent is not taxed unless somebody ticks the line.",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "additionalProperties": true
      },
      "WorkOrder": {
        "type": "object",
        "description": "One job on one aircraft. Shop data: only owners, admins and technicians can read it, on any aircraft (not dispatchers). Whether it has been invoiced or paid is read from its invoice (`billing`), never stored on the job.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Work order id."
          },
          "number": {
            "type": "integer",
            "description": "The organization's number for the job, from 1001."
          },
          "label": {
            "type": "string",
            "description": "How the job is written: `WO-1042`."
          },
          "status": {
            "type": "string",
            "description": "The stage the shop sets.",
            "enum": [
              "requested",
              "scheduled",
              "received",
              "in_progress",
              "waiting_owner",
              "waiting_parts",
              "ready",
              "completed",
              "cancelled"
            ]
          },
          "statusLabel": {
            "type": "string",
            "description": "The stage in words, e.g. `Waiting for parts`."
          },
          "complaint": {
            "description": "What the owner asked for, in their words.",
            "type": [
              "string",
              "null"
            ]
          },
          "openedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the job was opened."
          },
          "receivedAt": {
            "format": "date-time",
            "description": "When the aircraft arrived.",
            "type": [
              "string",
              "null"
            ]
          },
          "promisedOn": {
            "description": "The day the shop promised it back, `yyyy-mm-dd`.",
            "type": [
              "string",
              "null"
            ]
          },
          "completedAt": {
            "format": "date-time",
            "description": "When the work was completed (the completed stage). Null on a cancelled or reopened job.",
            "type": [
              "string",
              "null"
            ]
          },
          "closedAt": {
            "format": "date-time",
            "description": "When the job left the board, completed or cancelled. Null while open.",
            "type": [
              "string",
              "null"
            ]
          },
          "stageRank": {
            "type": "integer",
            "description": "The stage's position in the order a job moves through (requested is 0), for sorting."
          },
          "chargesCents": {
            "type": "integer",
            "description": "What the job's billable lines charge so far, discounts taken, before tax and fees."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last change."
          },
          "aircraft": {
            "type": "object",
            "description": "The aircraft.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Resource id."
              },
              "use": {
                "type": "string",
                "description": "`fleet` (the organization's own) or `shop` (a customer's).",
                "enum": [
                  "fleet",
                  "shop"
                ]
              },
              "archived": {
                "type": "boolean",
                "description": "The aircraft has since been archived."
              },
              "tailNumber": {
                "description": "Tail number.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "make": {
                "description": "Make.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "model": {
                "description": "Model.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "grounded": {
                "type": "boolean",
                "description": "Grounded right now."
              },
              "meterMode": {
                "description": "Which meters it has.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          },
          "billTo": {
            "description": "Who is billed. Defaults to the aircraft's primary owner; an admin or a dispatcher can change it until the job has a live invoice.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Org user id."
              },
              "name": {
                "description": "Name.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "external": {
                "type": "boolean",
                "description": "An owner from outside the organization."
              },
              "contactEmail": {
                "description": "Where the invoice is sent.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "claimed": {
                "type": "boolean",
                "description": "An owner from outside who has signed in: billed at their own login, with or without an address on file."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "technicians": {
            "type": "array",
            "description": "Technicians assigned.",
            "items": {
              "type": "object",
              "description": "A technician.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "Org user id."
                },
                "name": {
                  "description": "Name.",
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "additionalProperties": true
            }
          },
          "billing": {
            "type": "string",
            "description": "Read from the job's invoice: `none`, `invoiced` (not yet paid) or `paid`.",
            "enum": [
              "none",
              "invoiced",
              "paid"
            ]
          },
          "invoice": {
            "description": "The live (unvoided) invoice, when there is one.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Invoice id."
              },
              "total": {
                "type": "integer",
                "description": "Total in cents, tax included."
              },
              "tax": {
                "description": "Sales tax in cents.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "paidAt": {
                "format": "date-time",
                "description": "When it was paid.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "dueAt": {
                "format": "date-time",
                "description": "When it is due.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "number": {
                "description": "Stripe's invoice number.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "hobbsIn": {
            "description": "Hobbs at arrival, in tenths of an hour. On the single job only.",
            "type": [
              "integer",
              "null"
            ]
          },
          "tachIn": {
            "description": "Tach at arrival, in tenths.",
            "type": [
              "integer",
              "null"
            ]
          },
          "hobbsOut": {
            "description": "Hobbs at return to service, in tenths.",
            "type": [
              "integer",
              "null"
            ]
          },
          "tachOut": {
            "description": "Tach at return to service, in tenths.",
            "type": [
              "integer",
              "null"
            ]
          },
          "customerNotes": {
            "description": "Notes the owner may be shown.",
            "type": [
              "string",
              "null"
            ]
          },
          "internalNotes": {
            "description": "The shop's own notes.",
            "type": [
              "string",
              "null"
            ]
          },
          "holdOwnerNotices": {
            "type": "boolean",
            "description": "The owners are not told about this job's stages until somebody lifts the hold (or sends findings). On the single job only."
          },
          "aircraftTimes": {
            "type": "string",
            "description": "Only on the answer to a create or change that set readings in or out: `updated` when they moved the aircraft's own meters, `logged` when they were older than its newest reading and went in its log as history.",
            "enum": [
              "updated",
              "logged"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "createdBy": {
            "description": "Who opened it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Org user id."
              },
              "name": {
                "description": "Name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "booking": {
            "description": "The maintenance booking holding the hangar slot.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Reservation id."
              },
              "start": {
                "type": "string",
                "format": "date-time",
                "description": "Start."
              },
              "end": {
                "type": "string",
                "format": "date-time",
                "description": "End."
              },
              "cancelled": {
                "type": "boolean",
                "description": "The booking has been cancelled."
              },
              "moved": {
                "type": "boolean",
                "description": "The booking has since been moved to another aircraft or made something other than maintenance, so it is no longer this job's hangar slot."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "hasInvoices": {
            "type": "boolean",
            "description": "The job has been billed, voided bills included. Such a job stays on the record: it can be cancelled, not deleted."
          }
        },
        "additionalProperties": true
      },
      "WorkOrderItem": {
        "type": "object",
        "description": "One piece of work on a job. Who raised it (`source`) and what the owner said (`decision`) are separate. An item tied to an inspection or a squawk is done when that is signed off or resolved, wherever that happened; only a plain item is marked done on its own.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Item id."
          },
          "source": {
            "type": "string",
            "description": "Who raised it: the owner asked (`requested`) or the shop found it (`found`).",
            "enum": [
              "requested",
              "found"
            ]
          },
          "decision": {
            "description": "What the owner said on the phone. Null until asked.",
            "enum": [
              "approved",
              "declined",
              "deferred",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": "string",
            "description": "The work.",
            "maxLength": 500
          },
          "position": {
            "type": "integer",
            "description": "Order on the job."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was added."
          },
          "done": {
            "type": "boolean",
            "description": "The work is done."
          },
          "doneAt": {
            "format": "date-time",
            "description": "When it was done.",
            "type": [
              "string",
              "null"
            ]
          },
          "doneBy": {
            "description": "Who did it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Org user id."
              },
              "name": {
                "description": "Name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "doneVia": {
            "type": "string",
            "description": "Where done is read from.",
            "enum": [
              "item",
              "inspection",
              "squawk"
            ]
          },
          "inspection": {
            "description": "The inspection it discharges.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Reminder id."
              },
              "name": {
                "description": "Inspection name.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "signedOff": {
                "type": "boolean",
                "description": "Signed off."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "squawk": {
            "description": "The squawk it fixes.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Squawk id."
              },
              "title": {
                "type": "string",
                "description": "Title."
              },
              "resolved": {
                "type": "boolean",
                "description": "Resolved."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "approval": {
            "description": "The phone call the decision was taken on.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Call id."
              },
              "contactName": {
                "type": "string",
                "description": "Who was reached."
              },
              "contactedAt": {
                "type": "string",
                "format": "date-time",
                "description": "When."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "sentToOwnerAt": {
            "format": "date-time",
            "description": "When the shop sent this finding to the owners. Until then the owners are not shown it or asked about it.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "WorkOrderApproval": {
        "type": "object",
        "description": "A record of a phone call with the owner: who was reached, when, the spend limit they gave, notes, and the items it decided. Not an owner login or an e-signature.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Call id."
          },
          "contactName": {
            "type": "string",
            "description": "Who was reached, as typed."
          },
          "contactedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When."
          },
          "spendLimitCents": {
            "description": "The most they agreed to spend, in cents.",
            "type": [
              "integer",
              "null"
            ]
          },
          "notes": {
            "description": "Notes.",
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was recorded."
          },
          "recordedBy": {
            "description": "Who recorded it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Org user id."
              },
              "name": {
                "description": "Name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "description": "The items this call decided (an item decided again later points at the later call).",
            "items": {
              "type": "object",
              "description": "An item.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "Item id."
                },
                "description": {
                  "type": "string",
                  "description": "The work."
                },
                "decision": {
                  "description": "Its decision.",
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "WorkOrderUnagreedCharge": {
        "type": "object",
        "description": "A finding on a job's bill that the owner has not agreed to, and what its billable lines come to.",
        "properties": {
          "itemId": {
            "type": "integer",
            "description": "Item id."
          },
          "description": {
            "type": "string",
            "description": "The finding."
          },
          "chargesCents": {
            "type": "integer",
            "description": "What its billable lines charge, discounts taken, before tax."
          }
        },
        "additionalProperties": true
      },
      "WorkOrderLine": {
        "type": "object",
        "description": "One line on a job: labor, a part, outside work, shop supplies, freight, a fee or other. Becomes one invoice line when the job is invoiced. `unitPriceCents` is what the customer pays per unit; cost and markup never reach the invoice.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Line id."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was added."
          },
          "category": {
            "type": "string",
            "description": "What kind of line, the invoice line category that decides its sales tax.",
            "enum": [
              "labor",
              "part",
              "supply",
              "outside_service",
              "freight",
              "fee",
              "other"
            ]
          },
          "description": {
            "type": "string",
            "description": "What it is.",
            "maxLength": 200
          },
          "position": {
            "type": "integer",
            "description": "Order on the job."
          },
          "qty": {
            "type": "integer",
            "description": "Quantity. Always 1 for labor."
          },
          "costCents": {
            "description": "What the shop paid, per unit, in cents.",
            "type": [
              "integer",
              "null"
            ]
          },
          "markupBps": {
            "description": "The markup applied, in basis points: 1500 is 15%.",
            "type": [
              "integer",
              "null"
            ]
          },
          "unitPriceCents": {
            "type": "integer",
            "description": "The customer's price per unit, in cents, before any discount."
          },
          "discountBps": {
            "description": "An admin's discount on the line, in basis points (1000 is 10% off), taken off the whole line before tax. A line discounted to nothing stays on the invoice at $0.00.",
            "type": [
              "integer",
              "null"
            ]
          },
          "totalCents": {
            "type": "integer",
            "description": "What the line charges: qty x unit price less the discount, rounded once on the line; zero when not billable."
          },
          "taxable": {
            "description": "Decided on this line; null follows the organization's rule for the category.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "billable": {
            "type": "boolean",
            "description": "Charged on the invoice. False records the work without billing it."
          },
          "minutes": {
            "description": "Labor: clock minutes.",
            "type": [
              "integer",
              "null"
            ]
          },
          "hours": {
            "description": "Labor: the minutes as hours, e.g. `1.5 h`.",
            "type": [
              "string",
              "null"
            ]
          },
          "rateCents": {
            "description": "Labor: the hourly rate in cents.",
            "type": [
              "integer",
              "null"
            ]
          },
          "workedOn": {
            "description": "Labor: the day, `yyyy-mm-dd`.",
            "type": [
              "string",
              "null"
            ]
          },
          "technician": {
            "description": "Labor: who did it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Org user id."
              },
              "name": {
                "description": "Name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "partNumber": {
            "description": "Part number.",
            "type": [
              "string",
              "null"
            ]
          },
          "serialNumber": {
            "description": "Serial number.",
            "type": [
              "string",
              "null"
            ]
          },
          "vendor": {
            "description": "Where it came from.",
            "type": [
              "string",
              "null"
            ]
          },
          "partStatus": {
            "description": "The part's state.",
            "enum": [
              "ordered",
              "received",
              "installed",
              "returned",
              "unused",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "orderedOn": {
            "description": "When the part was ordered, `yyyy-mm-dd`.",
            "type": [
              "string",
              "null"
            ]
          },
          "expectedOn": {
            "description": "When the part is due, `yyyy-mm-dd`.",
            "type": [
              "string",
              "null"
            ]
          },
          "itemId": {
            "description": "The item this line belongs to.",
            "type": [
              "integer",
              "null"
            ]
          },
          "createdByOrgUserId": {
            "description": "Who entered the line. Somebody who may not set prices changes only their own lines.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "WorkOrderSettings": {
        "type": "object",
        "description": "An organization's defaults for pricing lines. A line keeps the figures it was priced at.",
        "properties": {
          "laborRateCents": {
            "description": "Shop labor rate per hour, in cents. Fills in each labor entry.",
            "type": [
              "integer",
              "null"
            ]
          },
          "partsMarkupBps": {
            "description": "Default markup on parts, in basis points: 1500 is 15%.",
            "type": [
              "integer",
              "null"
            ]
          },
          "outsideWorkMarkupBps": {
            "description": "Default markup on outside work, in basis points.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "WorkOrderFile": {
        "type": "object",
        "description": "A photo or document on a job, or on one of its items.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The file id."
          },
          "label": {
            "description": "What it is, when somebody named it.",
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "type": "string",
            "description": "`shop` (the shop's own) or `owner` (one the owner may be shown).",
            "enum": [
              "shop",
              "owner"
            ]
          },
          "url": {
            "description": "A link to the file that works for 15 minutes. Null when the stored object is missing.",
            "type": [
              "string",
              "null"
            ]
          },
          "fileName": {
            "type": "string",
            "description": "The name it was uploaded with."
          },
          "itemId": {
            "description": "The item it is on; null for the job itself.",
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "When it was attached.",
            "format": "date-time"
          },
          "uploadedBy": {
            "description": "Who attached it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Member id."
              },
              "name": {
                "description": "Their name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "InvoiceFile": {
        "type": "object",
        "description": "A file on an invoice: the vendor's invoice for an outside service, a receipt, an 8130-3. The person billed reads only the files shown to them, without `uploadedBy` or `workOrderFileId`.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The file id."
          },
          "label": {
            "description": "What it is, when somebody named it.",
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "type": "string",
            "description": "`shop` (the organization's own) or `owner` (shown to the person billed).",
            "enum": [
              "shop",
              "owner"
            ]
          },
          "url": {
            "description": "A link to the file that works for 15 minutes. Null when the stored object is missing.",
            "type": [
              "string",
              "null"
            ]
          },
          "fileName": {
            "type": "string",
            "description": "The name it was uploaded with."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was attached.",
            "format": "date-time"
          },
          "uploadedBy": {
            "description": "Who attached it. Administrators only.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Member id."
              },
              "name": {
                "description": "Their name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "workOrderFileId": {
            "description": "The job file it was copied from with Add from the job. Administrators only.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "AircraftHistoryEntry": {
        "type": "object",
        "description": "One thing done to an aircraft: an inspection signed off, a squawk resolved, or a job completed. Read side by side from their own records, never stored as one.",
        "properties": {
          "key": {
            "type": "string",
            "description": "`<kind>-<recordId>`: unique across the kinds."
          },
          "kind": {
            "type": "string",
            "description": "What it is.",
            "enum": [
              "inspection",
              "squawk",
              "work_order"
            ]
          },
          "recordId": {
            "type": "integer",
            "description": "The compliance record, squawk or work order id."
          },
          "date": {
            "type": "string",
            "description": "When it was signed off, resolved or finished.",
            "format": "date-time"
          },
          "title": {
            "type": "string",
            "description": "The inspection's name, the squawk's title, or the job's request."
          },
          "reference": {
            "description": "What a mechanic would quote: an AD number not already in the title, or the job's `WO-` number.",
            "type": [
              "string",
              "null"
            ]
          },
          "detail": {
            "description": "The method of compliance, the resolution note, or what the shop wrote for the owner. At most 400 characters.",
            "type": [
              "string",
              "null"
            ]
          },
          "by": {
            "description": "Who signed it off (with the certificate, for the shop), resolved it, or did the work. Never on an owner's job row.",
            "type": [
              "string",
              "null"
            ]
          },
          "hobbs": {
            "description": "Hobbs at the time, in tenths of an hour. Null when unknown.",
            "type": [
              "integer",
              "null"
            ]
          },
          "tach": {
            "description": "Tach at the time, in tenths of an hour. Null when unknown.",
            "type": [
              "integer",
              "null"
            ]
          },
          "link": {
            "type": "object",
            "description": "The record to open: one of these, or none on the owner's inspection rows.",
            "properties": {
              "inspectionId": {
                "type": "integer",
                "description": "The inspection (maintenance reminder) the sign-off was for."
              },
              "squawkId": {
                "type": "integer",
                "description": "The squawk."
              },
              "workOrderId": {
                "type": "integer",
                "description": "The work order."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "AircraftInvoice": {
        "type": "object",
        "description": "A bill tied to an aircraft: for a booking that flew it, or for a shop job on it.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The invoice id."
          },
          "number": {
            "description": "The invoice number.",
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "When it was raised.",
            "format": "date-time"
          },
          "dueAt": {
            "description": "When it is due.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "paidAt": {
            "description": "When it was paid.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "What the bill is now.",
            "enum": [
              "paid",
              "open",
              "overdue",
              "void",
              "refunded"
            ]
          },
          "totalCents": {
            "type": "integer",
            "description": "The total, in cents."
          },
          "purpose": {
            "type": "string",
            "description": "`flight` or `prepaid_package` for a booking's bill, `work_order` for a shop job's."
          },
          "billedTo": {
            "type": "object",
            "description": "Who it is billed to.",
            "properties": {
              "orgUserId": {
                "description": "Their member id; null for a guest.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "name": {
                "description": "Their name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          },
          "for": {
            "description": "What it is for: `{kind: reservation, id, title, start}` or `{kind: work_order, id, label}`.",
            "properties": {
              "kind": {
                "type": "string",
                "description": "The booking or the job.",
                "enum": [
                  "reservation",
                  "work_order"
                ]
              },
              "id": {
                "type": "integer",
                "description": "The reservation or work order id."
              },
              "title": {
                "description": "The booking's title.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "start": {
                "description": "When the booking started.",
                "format": "date-time",
                "type": [
                  "string",
                  "null"
                ]
              },
              "label": {
                "type": "string",
                "description": "The job's `WO-` number."
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "ComponentLife": {
        "type": "object",
        "description": "How much of a component's life is used and left, computed on read from the aircraft's meter. All hours are TENTHS.",
        "properties": {
          "meter": {
            "type": "string",
            "description": "The aircraft meter its time counts on.",
            "enum": [
              "tach",
              "hobbs"
            ]
          },
          "meterNow": {
            "description": "That meter now, or when it came off. Null when the aircraft has no such meter.",
            "type": [
              "integer",
              "null"
            ]
          },
          "timeNow": {
            "description": "Its own time now (or when it came off), since new or overhaul: `timeAtInstall` plus what the meter has moved since it went on, never less.",
            "type": [
              "integer",
              "null"
            ]
          },
          "hours": {
            "description": "The hour limit, when it has one.",
            "properties": {
              "limit": {
                "type": "integer",
                "description": "The limit."
              },
              "left": {
                "description": "Time left before the limit; zero or below is past it.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "dueAtMeter": {
                "description": "The aircraft reading at which the limit is reached: the linked inspection's `remindAtHours`.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "warnWithin": {
                "type": "integer",
                "description": "Amber inside this: 10% of the limit or 100 (10.0 hours), whichever is more. The linked inspection's warning."
              },
              "status": {
                "description": "ok, inside the warning, or at or past the limit.",
                "enum": [
                  "ok",
                  "dueSoon",
                  "overdue",
                  null
                ],
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "calendar": {
            "description": "The calendar limit, when it has one: counted from `sinceOn` (else `installedOn`) to the same day that many months later.",
            "properties": {
              "limitMonths": {
                "type": "integer",
                "description": "The limit in months."
              },
              "dueOn": {
                "type": "string",
                "description": "The day the limit is reached, YYYY-MM-DD; past it on that day.",
                "format": "date"
              },
              "daysLeft": {
                "type": "integer",
                "description": "Whole days until `dueOn`."
              },
              "warnWithin": {
                "type": "integer",
                "description": "Amber inside this many days: 10% of its calendar life or 30, whichever is more."
              },
              "status": {
                "type": "string",
                "description": "ok, inside the warning, or at or past the limit.",
                "enum": [
                  "ok",
                  "dueSoon",
                  "overdue"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "Where it stands: the clock nearer its limit decides. `none` without a limit that can be counted; `removed` once it came off.",
            "enum": [
              "ok",
              "dueSoon",
              "overdue",
              "none",
              "removed"
            ]
          },
          "binding": {
            "description": "Which clock decided `status`.",
            "enum": [
              "hours",
              "calendar",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "AircraftComponent": {
        "type": "object",
        "description": "A life-limited part on an aircraft (a magneto, a propeller, an engine): the time it had when it went on and the limit it may not pass. Each limit is tracked by a linked one-off inspection (see `componentClock` on an inspection), so it warns, reminds owners and grounds like any other inspection.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "What it is.",
            "example": "Left magneto",
            "maxLength": 60
          },
          "position": {
            "description": "Where it is: Engine, Propeller, or free text.",
            "maxLength": 60,
            "type": [
              "string",
              "null"
            ]
          },
          "partNumber": {
            "description": "Part number.",
            "maxLength": 40,
            "type": [
              "string",
              "null"
            ]
          },
          "serialNumber": {
            "description": "Serial number.",
            "maxLength": 40,
            "type": [
              "string",
              "null"
            ]
          },
          "installedOn": {
            "type": "string",
            "description": "The day it went on, YYYY-MM-DD.",
            "format": "date"
          },
          "installedAtMeter": {
            "description": "The aircraft's reading on `meter` when it went on, in tenths. Null on an aircraft with no meters.",
            "type": [
              "integer",
              "null"
            ]
          },
          "meter": {
            "type": "string",
            "description": "The aircraft meter its time counts on.",
            "enum": [
              "tach",
              "hobbs"
            ]
          },
          "timeAtInstall": {
            "type": "integer",
            "description": "Its own time when it went on, in tenths, since new or overhaul."
          },
          "timeSince": {
            "type": "string",
            "description": "What `timeAtInstall` counts from.",
            "enum": [
              "new",
              "overhaul"
            ]
          },
          "sinceOn": {
            "description": "The day it was new or last overhauled; the calendar limit counts from here. Null means `installedOn`.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "limitHours": {
            "description": "The limit on its own time, in tenths.",
            "type": [
              "integer",
              "null"
            ]
          },
          "limitMonths": {
            "description": "The calendar limit, in months.",
            "type": [
              "integer",
              "null"
            ]
          },
          "groundAtLimit": {
            "type": "boolean",
            "description": "Whether reaching a limit grounds the aircraft (copied onto the linked inspections)."
          },
          "notes": {
            "description": "The shop's notes. Never shown to the aircraft's owner.",
            "maxLength": 2000,
            "type": [
              "string",
              "null"
            ]
          },
          "removedOn": {
            "description": "The day it came off. Its time stops there and its inspections are retired.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "removedAtMeter": {
            "description": "The aircraft's reading when it came off, in tenths.",
            "type": [
              "integer",
              "null"
            ]
          },
          "life": {
            "$ref": "#/components/schemas/ComponentLife"
          },
          "inspections": {
            "type": "array",
            "description": "The inspections tracking its limits: `reminderId` while open, `signedOffAt` once signed off.",
            "items": {
              "type": "object",
              "description": "A linked inspection.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "The inspection (template) id."
                },
                "clock": {
                  "type": "string",
                  "description": "Which limit.",
                  "enum": [
                    "hours",
                    "date"
                  ]
                },
                "reminderId": {
                  "description": "Its open cycle on the aircraft.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "signedOffAt": {
                  "format": "date-time",
                  "description": "When its latest cycle was signed off.",
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "CustomerFile": {
        "type": "object",
        "description": "A file on a customer's account: a signed authorization, insurance, correspondence.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The file id."
          },
          "label": {
            "description": "What it is, when somebody named it.",
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "type": "string",
            "description": "`shop` (the shop's own) or `owner` (shown to the customer).",
            "enum": [
              "shop",
              "owner"
            ]
          },
          "url": {
            "description": "A link to the file that works for 15 minutes. Null when the stored object is missing.",
            "type": [
              "string",
              "null"
            ]
          },
          "fileName": {
            "type": "string",
            "description": "The name it was uploaded with."
          },
          "createdAt": {
            "type": "string",
            "description": "When it was added.",
            "format": "date-time"
          },
          "uploadedBy": {
            "description": "Who added it.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Member id."
              },
              "name": {
                "description": "Their name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "fromCustomer": {
            "type": "boolean",
            "description": "The customer added it to their own account. It stays visible to them."
          }
        },
        "additionalProperties": true
      },
      "OwnFile": {
        "type": "object",
        "description": "A file on the caller's own account, as they see it.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The file id."
          },
          "name": {
            "type": "string",
            "description": "Its label, or the name it was uploaded with."
          },
          "fileName": {
            "type": "string",
            "description": "The name it was uploaded with."
          },
          "url": {
            "description": "A link to the file that works for 15 minutes. Null when the stored object is missing.",
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "When it was added.",
            "format": "date-time"
          },
          "yours": {
            "type": "boolean",
            "description": "You added it; you may remove it."
          }
        },
        "additionalProperties": true
      },
      "LedgerEntry": {
        "type": "object",
        "description": "One append-only money movement on a member's account. Corrections are reversing entries, never edits. Partial top-up refunds link via `refundsTopupId`.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the entry was posted."
          },
          "amountCents": {
            "type": "integer",
            "description": "Signed cents. Credits positive; charges negative."
          },
          "type": {
            "type": "string",
            "description": "topup | cash | check | other | adjustment | flight_charge | item_charge | fee | refund | reversal"
          },
          "memo": {
            "description": "Optional note.",
            "type": [
              "string",
              "null"
            ]
          },
          "orgUserId": {
            "type": "integer",
            "description": "Member this entry belongs to."
          },
          "organizationId": {
            "type": "integer",
            "description": "Organization id."
          },
          "createdByOrgUserId": {
            "description": "Who posted it, when applicable.",
            "type": [
              "integer",
              "null"
            ]
          },
          "createdBy": {
            "description": "The member who posted this entry, when a person did (desk credit, refund, close-out).",
            "properties": {
              "orgUserId": {
                "type": "integer",
                "description": "Poster org-user id."
              },
              "name": {
                "description": "Poster display name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "invoiceId": {
            "description": "Linked Stripe invoice when money moved through Stripe.",
            "type": [
              "integer",
              "null"
            ]
          },
          "reservationId": {
            "description": "Linked reservation for flight charges.",
            "type": [
              "integer",
              "null"
            ]
          },
          "reversesId": {
            "description": "Set on reversals: the entry this one reverses.",
            "type": [
              "integer",
              "null"
            ]
          },
          "stripePaymentIntentId": {
            "description": "Stripe PaymentIntent id on card top-ups.",
            "type": [
              "string",
              "null"
            ]
          },
          "refundMethod": {
            "description": "stripe | check_cash on refund entries.",
            "type": [
              "string",
              "null"
            ]
          },
          "stripeRefundId": {
            "description": "Stripe Refund id when money returned to the card.",
            "type": [
              "string",
              "null"
            ]
          },
          "refundsTopupId": {
            "description": "Top-up this refund draws down (partial refunds).",
            "type": [
              "integer",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "description": "Optional line items.",
            "items": {
              "$ref": "#/components/schemas/LedgerEntryItem"
            }
          }
        },
        "additionalProperties": true
      },
      "LedgerEntryItem": {
        "type": "object",
        "description": "One line on a ledger entry.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Line id."
          },
          "name": {
            "type": "string",
            "description": "What it is for."
          },
          "qty": {
            "type": "integer",
            "description": "Quantity."
          },
          "unitPrice": {
            "type": "integer",
            "description": "Unit price in cents (signed; qty × unitPrice rolls up to the entry amount)."
          }
        },
        "additionalProperties": true
      },
      "MemberLedger": {
        "type": "object",
        "description": "A member's account balance and recent ledger entries. Present whether or not the org has ledger mode enabled (balance is 0 with an empty list when unused).",
        "properties": {
          "balanceCents": {
            "type": "integer",
            "description": "Cached account balance in cents. Positive = credit; negative = amount owed."
          },
          "ledgerEnabled": {
            "type": "boolean",
            "description": "Whether this organization is in ledger billing mode."
          },
          "entries": {
            "type": "array",
            "description": "Newest-first page of entries.",
            "items": {
              "$ref": "#/components/schemas/LedgerEntry"
            }
          }
        },
        "additionalProperties": true
      },
      "LedgerStatement": {
        "type": "object",
        "description": "Inclusive period statement for one member. Opening is the sum of entries strictly before `start`. Closing is opening plus the period sum.",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Inclusive period start."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Inclusive period end."
          },
          "openingCents": {
            "type": "integer",
            "description": "Balance immediately before the period."
          },
          "closingCents": {
            "type": "integer",
            "description": "Balance at the end of the period."
          },
          "periodSumCents": {
            "type": "integer",
            "description": "Sum of period entry amounts."
          },
          "entries": {
            "type": "array",
            "description": "Oldest-first period entries, each with running balance.",
            "items": {
              "$ref": "#/components/schemas/LedgerStatementEntry"
            }
          }
        },
        "additionalProperties": true
      },
      "LedgerStatementEntry": {
        "type": "object",
        "description": "One statement line with running balance after the entry.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Ledger entry id."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the entry was posted."
          },
          "amountCents": {
            "type": "integer",
            "description": "Signed cents. Credits positive; charges negative."
          },
          "type": {
            "type": "string",
            "description": "Ledger entry type."
          },
          "memo": {
            "description": "Optional note.",
            "type": [
              "string",
              "null"
            ]
          },
          "runningBalanceCents": {
            "type": "integer",
            "description": "Balance after this entry, starting from the period opening."
          }
        },
        "additionalProperties": true
      },
      "LedgerReceipt": {
        "type": "object",
        "description": "Printable receipt for a ledger charge (flight / item / fee). Replaces Stripe PDF when the org is in ledger mode.",
        "properties": {
          "entry": {
            "$ref": "#/components/schemas/LedgerEntry"
          },
          "member": {
            "type": "object",
            "properties": {
              "orgUserId": {
                "type": "integer",
                "description": "Membership id."
              },
              "name": {
                "description": "Member display name.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "description": "Member email.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "orgUserId"
            ]
          },
          "organization": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Organization id."
              },
              "name": {
                "type": "string",
                "description": "Organization name."
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "reservation": {
            "description": "Linked booking when this is a flight charge.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Reservation id."
              },
              "start": {
                "type": "string",
                "format": "date-time",
                "description": "Booking start."
              },
              "end": {
                "type": "string",
                "format": "date-time",
                "description": "Booking end."
              },
              "type": {
                "description": "Reservation type.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "resourceLabel": {
                "description": "Aircraft tail or simulator name.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "start",
              "end"
            ],
            "type": [
              "object",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "LedgerAccount": {
        "type": "object",
        "description": "One member on the organization-wide ledger roster. Positive balanceCents is credit on account; negative is amount owed.",
        "properties": {
          "orgUserId": {
            "type": "integer",
            "description": "Membership id."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "email": {
            "description": "Member email.",
            "type": [
              "string",
              "null"
            ]
          },
          "balanceCents": {
            "type": "integer",
            "description": "Cached account balance in cents. Positive = credit; negative = amount owed."
          },
          "lastActivityAt": {
            "format": "date-time",
            "description": "When the latest ledger entry was posted.",
            "type": [
              "string",
              "null"
            ]
          },
          "owingSince": {
            "format": "date-time",
            "description": "When the running balance last crossed into negative and has stayed there. Null when they do not owe.",
            "type": [
              "string",
              "null"
            ]
          },
          "daysOwing": {
            "description": "Whole days from owingSince to now. Null when they do not owe.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "LedgerAccountSummary": {
        "type": "object",
        "description": "Org-wide ledger totals for the current (non-archived) roster. Not scoped to the list filter.",
        "properties": {
          "receivableCents": {
            "type": "integer",
            "description": "Sum of amounts owed (absolute value of negative balances)."
          },
          "creditOnAccountCents": {
            "type": "integer",
            "description": "Sum of positive balances."
          },
          "owingCount": {
            "type": "integer",
            "description": "Members with a negative balance."
          },
          "creditCount": {
            "type": "integer",
            "description": "Members with a positive balance."
          },
          "zeroCount": {
            "type": "integer",
            "description": "Members at zero."
          },
          "memberCount": {
            "type": "integer",
            "description": "Current members on the roster."
          }
        },
        "additionalProperties": true
      },
      "OrganizationLedgerSettings": {
        "type": "object",
        "description": "Org-wide ledger billing mode and optional card surcharge on member top-ups. Independent of Stripe Connect billing settings. Default off / no surcharge.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "When true, members use an account balance (top-ups, desk credit, refunds). Member flight and custom charges post to the ledger."
          },
          "topUpCardFeePercent": {
            "description": "Card surcharge on top-ups, hundredths of a percent (290 = 2.9%). Null or 0 = no percent. Does not apply to desk cash/check credit. Same units as invoice serviceFeePercent.",
            "type": [
              "integer",
              "null"
            ]
          },
          "topUpCardFeeFlatCents": {
            "description": "Flat cents added on each card top-up (30 = $0.30). Null or 0 = none. Stacks with the percent.",
            "type": [
              "integer",
              "null"
            ]
          },
          "lateFeePercent": {
            "description": "Whole percent of the amount owing (5 = 5%). Null or 0 = no percent. Ledger mode only.",
            "type": [
              "integer",
              "null"
            ]
          },
          "lateFeeFlatCents": {
            "description": "Flat cents added on each monthly late-fee post. Null or 0 = none.",
            "type": [
              "integer",
              "null"
            ]
          },
          "lateFeeGraceDays": {
            "description": "Days a balance may stay negative before a late fee posts. Null = 30 when a percent or flat is set. 0 = fee on the first eligible sweep after they go owing.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "LedgerAutoRefill": {
        "type": "object",
        "description": "Per-member auto-refill (ledger Auto Pay). Charges the default saved card on a cadence. Ledger mode only.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "When true, the hourly cron may charge this member."
          },
          "mode": {
            "type": "string",
            "description": "under_threshold (balance < threshold, charge chargeCents), pay_balance (charge abs owing), or fixed_amount (charge chargeCents even at zero).",
            "enum": [
              "under_threshold",
              "pay_balance",
              "fixed_amount"
            ]
          },
          "thresholdCents": {
            "description": "under_threshold: fire when balance is below this. 0 = fire once owing.",
            "type": [
              "integer",
              "null"
            ]
          },
          "chargeCents": {
            "description": "under_threshold and fixed_amount: ledger credit to charge (before card surcharge). Minimum 100.",
            "type": [
              "integer",
              "null"
            ]
          },
          "cadence": {
            "type": "string",
            "description": "daily or monthly.",
            "enum": [
              "daily",
              "monthly"
            ]
          },
          "monthlyDay": {
            "description": "1–28 when cadence is monthly.",
            "type": [
              "integer",
              "null"
            ]
          },
          "runHourLocal": {
            "type": "integer",
            "description": "Hour 0–23 in the organization time zone. First attempt waits until this hour. Default 4."
          },
          "lastAttemptAt": {
            "format": "date-time",
            "description": "Last charge attempt.",
            "type": [
              "string",
              "null"
            ]
          },
          "lastSuccessAt": {
            "format": "date-time",
            "description": "Last successful charge. Used for period idempotency.",
            "type": [
              "string",
              "null"
            ]
          },
          "consecutiveFailures": {
            "type": "integer",
            "description": "Failed attempts in the current period."
          },
          "nextEligibleAt": {
            "format": "date-time",
            "description": "Earliest retry after a failure.",
            "type": [
              "string",
              "null"
            ]
          },
          "pausedReason": {
            "description": "Why enabled is false after a pause.",
            "enum": [
              "declined_card",
              "needs_authentication",
              "no_payment_method",
              "org_not_connected",
              "stripe_unavailable",
              "disabled_by_member",
              "disabled_by_admin",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "LedgerTopUpIntent": {
        "type": "object",
        "description": "Client secrets for a ledger card top-up PaymentIntent on the org's connected Stripe account.",
        "properties": {
          "clientSecret": {
            "type": "string",
            "description": "PaymentIntent client secret for Stripe.js / Payment Element."
          },
          "paymentIntentId": {
            "type": "string",
            "description": "PaymentIntent id."
          },
          "orgStripeAccountId": {
            "type": "string",
            "description": "Connected account the Intent lives on."
          },
          "customerId": {
            "type": "string",
            "description": "Stripe customer id on that connected account."
          },
          "ephemeralKey": {
            "description": "Ephemeral key for native Payment Sheet. Null when already confirmed.",
            "type": [
              "string",
              "null"
            ]
          },
          "confirmed": {
            "type": "boolean",
            "description": "True when the server confirmed with a saved paymentMethodId."
          },
          "creditCents": {
            "type": "integer",
            "description": "Amount credited to the member ledger."
          },
          "feeCents": {
            "type": "integer",
            "description": "Org card surcharge included in the charge (0 when none)."
          },
          "chargeCents": {
            "type": "integer",
            "description": "Total charged to the card = creditCents + feeCents."
          },
          "balanceCents": {
            "type": "integer",
            "description": "Current ledger balance after a saved-card confirm. Omitted when the Intent still needs Stripe.js."
          }
        },
        "additionalProperties": true
      },
      "LedgerTopUpConfirm": {
        "type": "object",
        "description": "Ledger balance after Stripe.js confirmed a card top-up PaymentIntent.",
        "properties": {
          "credited": {
            "type": "boolean",
            "description": "True when this call posted the credit. False when it was already posted."
          },
          "balanceCents": {
            "type": "integer",
            "description": "Current ledger balance in cents."
          }
        },
        "additionalProperties": true
      },
      "LedgerRefundableTopup": {
        "type": "object",
        "description": "A card top-up that still has refundable remainder for the desk Refund UI.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Ledger entry id of the top-up."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the top-up posted."
          },
          "amountCents": {
            "type": "integer",
            "description": "Original top-up amount in cents."
          },
          "refundedCents": {
            "type": "integer",
            "description": "Already refunded against this top-up."
          },
          "refundableCents": {
            "type": "integer",
            "description": "amountCents − refundedCents."
          },
          "stripePaymentIntentId": {
            "description": "PaymentIntent to refund via Stripe when present.",
            "type": [
              "string",
              "null"
            ]
          },
          "memo": {
            "description": "Top-up memo.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "LedgerRefundable": {
        "type": "object",
        "description": "Balance plus refundable top-ups for the desk Refund flow.",
        "properties": {
          "balanceCents": {
            "type": "integer",
            "description": "Current account balance in cents."
          },
          "ledgerEnabled": {
            "type": "boolean",
            "description": "Whether ledger mode is on."
          },
          "topups": {
            "type": "array",
            "description": "Newest-first card top-ups with remaining refundable cents.",
            "items": {
              "$ref": "#/components/schemas/LedgerRefundableTopup"
            }
          }
        },
        "additionalProperties": true
      },
      "MembershipPlan": {
        "type": "object",
        "description": "What an organization charges people to belong, a one-time join fee, recurring dues, or both. Tiers are plans: full, associate, social.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "Full member"
          },
          "description": {
            "description": "What the tier includes.",
            "type": [
              "string",
              "null"
            ]
          },
          "archivedAt": {
            "format": "date-time",
            "description": "When the plan was retired. Retired plans stop being offered to new members; everyone already on one keeps being billed.",
            "type": [
              "string",
              "null"
            ]
          },
          "joinFeeCents": {
            "description": "One-time fee to join, in cents. Null when there is none.",
            "example": 50000,
            "type": [
              "integer",
              "null"
            ]
          },
          "joinFeeLabel": {
            "description": "How the join fee reads on the invoice.",
            "type": [
              "string",
              "null"
            ]
          },
          "duesCents": {
            "description": "Recurring dues, in cents. Null for a plan with no ongoing cost.",
            "example": 9500,
            "type": [
              "integer",
              "null"
            ]
          },
          "duesLabel": {
            "description": "How the dues read on the invoice.",
            "type": [
              "string",
              "null"
            ]
          },
          "duesInterval": {
            "type": "string",
            "description": "How often dues fall due.",
            "enum": [
              "monthly",
              "quarterly",
              "annual"
            ],
            "example": "monthly"
          },
          "duesDayOfMonth": {
            "description": "The day every member on this plan is billed, 1-28. Null bills each member on their own join anniversary instead.",
            "example": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "prorateFirstPeriod": {
            "type": "boolean",
            "description": "Whether somebody joining mid-cycle is charged a part-period for the days they actually get."
          },
          "autoBillDues": {
            "type": "boolean",
            "description": "Whether each period's invoice is raised automatically, overnight."
          },
          "duesDueInDays": {
            "description": "Days to pay a dues or joining-fee invoice. Null means no due date.",
            "type": [
              "integer",
              "null"
            ]
          },
          "bookingWindowDays": {
            "description": "How far ahead a member on this tier may book. Null means no limit.",
            "type": [
              "integer",
              "null"
            ]
          },
          "memberCount": {
            "type": "integer",
            "description": "How many live memberships are on this plan. Not its history."
          }
        },
        "additionalProperties": true
      },
      "MembershipPlanRate": {
        "type": "object",
        "description": "What one tier pays per hour for one aircraft, overriding the aircraft's published rate. Sparse: only the aircraft a plan disagrees with have a row.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "resourceId": {
            "type": "integer",
            "description": "The aircraft's resource id."
          },
          "dryRate": {
            "description": "Cents per hour, dry.",
            "type": [
              "integer",
              "null"
            ]
          },
          "wetRate": {
            "description": "Cents per hour, wet. Wins over dry, matching ordinary pricing.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Membership": {
        "type": "object",
        "description": "One person's membership of one organization. The money is a **snapshot** taken when it started, so re-pricing the plan never re-prices this member.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "status": {
            "type": "string",
            "description": "`pending`, set up, not started, bills nothing. `active`, the only status that accrues dues. `suspended`, paused, keeps the row and stops the meter. `cancelled`, over, and terminal.",
            "enum": [
              "pending",
              "active",
              "suspended",
              "cancelled"
            ]
          },
          "startedAt": {
            "format": "date-time",
            "description": "When the membership began.",
            "type": [
              "string",
              "null"
            ]
          },
          "endedAt": {
            "format": "date-time",
            "description": "When it was cancelled.",
            "type": [
              "string",
              "null"
            ]
          },
          "endedReason": {
            "description": "Why it ended or paused.",
            "type": [
              "string",
              "null"
            ]
          },
          "joinFeeCents": {
            "description": "The join fee as it stood on the day this member joined, in cents.",
            "type": [
              "integer",
              "null"
            ]
          },
          "joinFeeStatus": {
            "type": "string",
            "description": "`none`, `owed`, never invoiced, or `invoiced`.",
            "enum": [
              "none",
              "owed",
              "invoiced"
            ]
          },
          "duesCents": {
            "description": "Dues as they stood on the day this member joined, in cents.",
            "type": [
              "integer",
              "null"
            ]
          },
          "duesInterval": {
            "type": "string",
            "description": "This membership's cadence.",
            "enum": [
              "monthly",
              "quarterly",
              "annual"
            ]
          },
          "nextDueAt": {
            "format": "date-time",
            "description": "The start of the next period that has not been charged. **The billing cursor**: a period is owed because this says so, never because of what day it is. Null on a membership that is not accruing dues.",
            "type": [
              "string",
              "null"
            ]
          },
          "autoBillDues": {
            "type": "boolean",
            "description": "Whether this member's periods are invoiced automatically."
          },
          "agreementOnFileAt": {
            "format": "date-time",
            "description": "When staff recorded that the member's agreement is on file. A record only, nothing is gated on it.",
            "type": [
              "string",
              "null"
            ]
          },
          "plan": {
            "$ref": "#/components/schemas/MembershipPlan"
          },
          "orgUser": {
            "$ref": "#/components/schemas/OrganizationUser"
          },
          "charges": {
            "type": "array",
            "description": "Recent dues periods. Present on a single-membership read.",
            "items": {
              "$ref": "#/components/schemas/MembershipCharge"
            }
          }
        },
        "additionalProperties": true
      },
      "MembershipCharge": {
        "type": "object",
        "description": "One dues period, and what happened to it. The period is half-open: `periodEnd` is the first day NOT covered.",
        "properties": {
          "periodStart": {
            "type": "string",
            "format": "date-time",
            "description": "First day of the period."
          },
          "periodEnd": {
            "type": "string",
            "format": "date-time",
            "description": "Day after the last day of the period."
          },
          "amountCents": {
            "type": "integer",
            "description": "What was charged, in cents. A prorated part-period differs from the membership's dues."
          },
          "status": {
            "type": "string",
            "description": "`pending`, owed, not yet invoiced. `billed`, an invoice exists. `waived`, deliberately not charged. `failed`, billing was attempted and refused, and the row is kept so it can be retried.",
            "enum": [
              "pending",
              "billed",
              "waived",
              "failed"
            ]
          },
          "note": {
            "description": "Why, when it is not a plain charge.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Announcement": {
        "type": "object",
        "description": "A notice shown to members.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "title": {
            "type": "string",
            "description": "Headline."
          },
          "message": {
            "type": "string",
            "description": "Body text."
          },
          "expireAt": {
            "format": "date-time",
            "description": "When it stops being shown. Expired notices are swept hourly.",
            "type": [
              "string",
              "null"
            ]
          },
          "forRoles": {
            "type": "array",
            "description": "Roles that should see it. Empty means everyone.",
            "items": {
              "type": "string"
            }
          },
          "seenAt": {
            "format": "date-time",
            "description": "When the signed-in member marked this notice as seen (Got it). Null if they have not. Seen notices leave Home and stay on the announcements page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Notification": {
        "type": "object",
        "description": "An in-app notification for one member.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "title": {
            "type": "string",
            "description": "Headline."
          },
          "subtitle": {
            "description": "Secondary line.",
            "type": [
              "string",
              "null"
            ]
          },
          "link": {
            "description": "Deep link into the app or console.",
            "type": [
              "string",
              "null"
            ]
          },
          "readAt": {
            "format": "date-time",
            "description": "When it was read. Null while unread.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "CurrencyType": {
        "type": "object",
        "description": "A recurring requirement members must stay current on, a flight review, a medical, a checkout.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "Flight Review"
          },
          "description": {
            "description": "What it covers.",
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "Whether it is being tracked."
          },
          "expiresInDays": {
            "description": "Validity in days.",
            "type": [
              "integer",
              "null"
            ]
          },
          "expiresInMonths": {
            "description": "Validity in months.",
            "example": 24,
            "type": [
              "integer",
              "null"
            ]
          },
          "expiresOn": {
            "format": "date-time",
            "description": "A fixed expiry date, for one-off requirements.",
            "type": [
              "string",
              "null"
            ]
          },
          "warningPeriodInDays": {
            "description": "How long before expiry to start warning.",
            "type": [
              "integer",
              "null"
            ]
          },
          "canFlyWithInstructor": {
            "type": "boolean",
            "description": "Whether a lapsed member may fly with a current instructor."
          },
          "canRenewSelf": {
            "type": "boolean",
            "description": "Whether a member may sign off their own renewal."
          },
          "instructorCanRenew": {
            "type": "boolean",
            "description": "Whether an instructor may sign it off."
          },
          "dispatcherCanRenew": {
            "type": "boolean",
            "description": "Whether a dispatcher may sign it off."
          },
          "orgUserGroups": {
            "type": "array",
            "description": "Member groups covered by this rule.",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          },
          "resourceGroups": {
            "type": "array",
            "description": "Aircraft groups covered by this rule.",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          },
          "documentTypes": {
            "type": "array",
            "description": "Documents required to satisfy this rule.",
            "items": {
              "$ref": "#/components/schemas/DocumentType"
            }
          }
        },
        "additionalProperties": true
      },
      "Currency": {
        "type": "object",
        "description": "One member's standing against one currency type. Created automatically when a type is attached to a member group, there is no endpoint that creates one directly.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "startedAt": {
            "format": "date-time",
            "description": "When the current period began.",
            "type": [
              "string",
              "null"
            ]
          },
          "warnedAt": {
            "format": "date-time",
            "description": "When it entered its warning period.",
            "type": [
              "string",
              "null"
            ]
          },
          "expiredAt": {
            "format": "date-time",
            "description": "When the server marked it lapsed.",
            "type": [
              "string",
              "null"
            ]
          },
          "archivedAt": {
            "format": "date-time",
            "description": "When a newer period replaced this record.",
            "type": [
              "string",
              "null"
            ]
          },
          "renewedBy": {
            "$ref": "#/components/schemas/OrganizationUser"
          },
          "orgUser": {
            "$ref": "#/components/schemas/OrganizationUser"
          },
          "currencyType": {
            "$ref": "#/components/schemas/CurrencyType"
          }
        },
        "additionalProperties": true
      },
      "CurrencyRuleDetail": {
        "type": "object",
        "description": "One currency rule with its live member standings and document completeness.",
        "properties": {
          "type": {
            "$ref": "#/components/schemas/CurrencyType"
          },
          "summary": {
            "type": "object",
            "description": "Counts across every active member record for this rule.",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Members covered."
              },
              "current": {
                "type": "integer",
                "description": "Current members."
              },
              "expiring": {
                "type": "integer",
                "description": "Members inside the warning period."
              },
              "expired": {
                "type": "integer",
                "description": "Lapsed members."
              },
              "notSignedOff": {
                "type": "integer",
                "description": "Members who have never been signed off."
              },
              "missingDocuments": {
                "type": "integer",
                "description": "Members missing at least one required document type."
              }
            },
            "additionalProperties": true
          },
          "members": {
            "type": "array",
            "description": "The requested page of member standings, attention first.",
            "items": {
              "type": "object",
              "description": "One member's standing against this rule.",
              "properties": {
                "currencyId": {
                  "type": "integer",
                  "description": "The renewable currency record id."
                },
                "orgUser": {
                  "$ref": "#/components/schemas/OrganizationUser"
                },
                "status": {
                  "type": "string",
                  "description": "The server-authoritative standing.",
                  "enum": [
                    "current",
                    "expiring",
                    "expired",
                    "notSignedOff"
                  ]
                },
                "startedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When this period began."
                },
                "warnedAt": {
                  "format": "date-time",
                  "description": "When warning began.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "expiredAt": {
                  "format": "date-time",
                  "description": "When it lapsed.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "expiresOn": {
                  "format": "date-time",
                  "description": "The projected rule-based expiry, when the rule has one.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "renewedBy": {
                  "$ref": "#/components/schemas/OrganizationUser"
                },
                "documents": {
                  "type": "array",
                  "description": "Documents attached to this currency period.",
                  "items": {
                    "type": "object",
                    "description": "Attached document metadata.",
                    "properties": {
                      "id": {
                        "type": "integer",
                        "description": "The document id."
                      },
                      "createdAt": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When it was uploaded."
                      },
                      "expiresAt": {
                        "format": "date-time",
                        "description": "When the document lapses.",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "fileCount": {
                        "type": "integer",
                        "description": "How many files are attached."
                      },
                      "documentType": {
                        "$ref": "#/components/schemas/DocumentType"
                      }
                    },
                    "additionalProperties": true
                  }
                },
                "missingDocumentTypeIds": {
                  "type": "array",
                  "description": "Required document type ids not attached to this period.",
                  "items": {
                    "type": "integer"
                  }
                }
              },
              "additionalProperties": true
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "additionalProperties": true
      },
      "DocumentType": {
        "type": "object",
        "description": "A category of member document, medical, photo ID, endorsement.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name.",
            "example": "Medical Certificate"
          },
          "restricted": {
            "type": "boolean",
            "description": "When true, only administrators may upload documents of this type on a member's behalf."
          }
        },
        "additionalProperties": true
      },
      "UserDocument": {
        "type": "object",
        "description": "A file held against a member.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "File name."
          },
          "url": {
            "description": "Location of the file. Fetch through the signed-URL endpoint rather than linking directly.",
            "type": [
              "string",
              "null"
            ]
          },
          "expiresAt": {
            "format": "date-time",
            "description": "When the document lapses.",
            "type": [
              "string",
              "null"
            ]
          },
          "documentType": {
            "$ref": "#/components/schemas/DocumentType"
          },
          "orgUser": {
            "$ref": "#/components/schemas/OrganizationUser"
          }
        },
        "additionalProperties": true
      },
      "Group": {
        "type": "object",
        "description": "A named set of members or resources, used to scope requirements and permissions in bulk.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "description": {
            "description": "What the group is for.",
            "type": [
              "string",
              "null"
            ]
          },
          "addNewUsers": {
            "type": "boolean",
            "description": "Automatically add every new member."
          }
        },
        "additionalProperties": true
      },
      "Availability": {
        "type": "object",
        "description": "A window in which a person or resource is free.",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Window start."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Window end."
          }
        },
        "additionalProperties": true
      },
      "ReportDefinition": {
        "type": "object",
        "description": "One report in the catalog.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier to pass to `POST /reports/run`.",
            "example": "revenue"
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "description": {
            "type": "string",
            "description": "What the report answers."
          },
          "category": {
            "type": "string",
            "description": "Grouping key.",
            "enum": [
              "financial",
              "operations",
              "fleet",
              "shop",
              "people",
              "compliance"
            ]
          }
        },
        "additionalProperties": true
      },
      "ReportResult": {
        "type": "object",
        "description": "The output of a report run.",
        "properties": {
          "columns": {
            "type": "array",
            "description": "Column definitions, in display order.",
            "items": {
              "type": "object",
              "description": "A column.",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Field key."
                },
                "label": {
                  "type": "string",
                  "description": "Header."
                },
                "unit": {
                  "description": "Unit, where the value is not a plain number.",
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "additionalProperties": true
            }
          },
          "rows": {
            "type": "array",
            "description": "Result rows, keyed by column key.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "totals": {
            "type": "object",
            "description": "Column totals, where the column is summable.",
            "properties": {},
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "SearchResult": {
        "type": "object",
        "description": "One match, flattened to a common shape.",
        "properties": {
          "type": {
            "type": "string",
            "description": "Which kind of record this is.",
            "enum": [
              "person",
              "resource",
              "location",
              "rating",
              "reservation",
              "announcement",
              "currency",
              "document",
              "squawk",
              "course",
              "enrollment",
              "endorsement",
              "workorder"
            ]
          },
          "id": {
            "type": "integer",
            "description": "The record's id, within its own type."
          },
          "title": {
            "type": "string",
            "description": "The line to show.",
            "example": "N172TS"
          },
          "subtitle": {
            "description": "Context under the title, who, what, or where.",
            "type": [
              "string",
              "null"
            ]
          },
          "date": {
            "format": "date-time",
            "description": "The date this record turns on, unformatted. See `dateLabel`.",
            "type": [
              "string",
              "null"
            ]
          },
          "dateLabel": {
            "description": "What `date` means.",
            "example": "Expires",
            "type": [
              "string",
              "null"
            ]
          },
          "timeZone": {
            "description": "IANA zone `date` belongs to. Null means the reader's own.",
            "example": "America/Denver",
            "type": [
              "string",
              "null"
            ]
          },
          "badge": {
            "description": "Status chip.",
            "example": "Grounded",
            "type": [
              "string",
              "null"
            ]
          },
          "params": {
            "type": "object",
            "description": "Ids needed to open this record, keyed by name.",
            "properties": {},
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "SearchResults": {
        "type": "object",
        "description": "Matches across every entity type the caller may see.",
        "properties": {
          "q": {
            "type": "string",
            "description": "The query that was run. Empty when browsing."
          },
          "types": {
            "type": "array",
            "description": "Every type this caller may search, the full category list, not just the types that matched. Narrowed by role: a member who cannot read squawks never sees `squawk` here.",
            "items": {
              "type": "string",
              "enum": [
                "person",
                "resource",
                "location",
                "rating",
                "reservation",
                "announcement",
                "currency",
                "document",
                "squawk",
                "course",
                "enrollment",
                "endorsement",
                "workorder"
              ]
            }
          },
          "counts": {
            "type": "object",
            "description": "How many matched, per type.",
            "properties": {},
            "additionalProperties": true
          },
          "results": {
            "type": "array",
            "description": "The matches, grouped by type in the order of `types`.",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          }
        },
        "additionalProperties": true
      },
      "AuditEvent": {
        "type": "object",
        "description": "One recorded thing somebody did. Append-only: audit events are never edited or deleted.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "action": {
            "type": "string",
            "description": "Dotted and past tense: `reservation.rescheduled`, `invoice.voided`, `resource.grounded`. Stable: values are never renamed, because rows written months ago still carry the old string."
          },
          "entityType": {
            "type": "string",
            "description": "What kind of thing it happened to.",
            "enum": [
              "reservation",
              "invoice",
              "squawk",
              "resource",
              "orgUser",
              "joinRequest",
              "document",
              "inspection",
              "enrollment",
              "membership",
              "organization",
              "apiKey",
              "standbyInterest",
              "ledgerEntry",
              "workOrder"
            ]
          },
          "entityId": {
            "type": "integer",
            "description": "The id of that thing. Not a foreign key, an event outlives the row it describes, so this can point at something that has since been deleted."
          },
          "summary": {
            "type": "string",
            "nullable": true,
            "description": "A finished sentence, written when the event was recorded."
          },
          "changes": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Only the fields that moved, as `{ field: { from, to } }`."
          },
          "source": {
            "type": "string",
            "nullable": true,
            "description": "`web`, `ios`, `api`, or `system`. Null when it could not be determined."
          },
          "actor": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Who did it. Null for system-originated events."
          },
          "subject": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The member the event is about, which is usually not the actor."
          },
          "resource": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "The aircraft, simulator or room it touched, if any."
          }
        },
        "additionalProperties": true
      },
      "StandbyInterest": {
        "type": "object",
        "description": "A member's request to be considered when a matching scheduling slot opens.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "kind": {
            "type": "string",
            "description": "How this interest matches: one reservation, a time window, or standing criteria.",
            "enum": [
              "on_reservation",
              "open_window",
              "standing"
            ]
          },
          "status": {
            "type": "string",
            "description": "Current lifecycle state.",
            "enum": [
              "active",
              "fulfilled",
              "withdrawn",
              "expired"
            ]
          },
          "source": {
            "type": "string",
            "description": "Who created the interest.",
            "enum": [
              "member",
              "desk",
              "system"
            ]
          },
          "priority": {
            "type": "integer",
            "description": "Desk priority from 0 to 10."
          },
          "expiresAt": {
            "format": "date-time",
            "description": "When this interest stops matching, if set.",
            "type": [
              "string",
              "null"
            ]
          },
          "start": {
            "format": "date-time",
            "description": "Start of an open-window interest.",
            "type": [
              "string",
              "null"
            ]
          },
          "end": {
            "format": "date-time",
            "description": "End of an open-window interest.",
            "type": [
              "string",
              "null"
            ]
          },
          "reservationType": {
            "description": "Optional reservation type filter on the interest row.",
            "type": [
              "string",
              "null"
            ]
          },
          "criteria": {
            "type": [
              "object",
              "null"
            ],
            "description": "Standing match criteria. Empty lists mean any. Used when kind is standing.",
            "additionalProperties": true,
            "properties": {
              "daysOfWeek": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 6
                },
                "description": "0 = Sunday through 6 = Saturday."
              },
              "localTimeStart": {
                "type": "string",
                "description": "HH:mm local start bound."
              },
              "localTimeEnd": {
                "type": "string",
                "description": "HH:mm local end bound."
              },
              "resourceIds": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "resourceTypes": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "plane",
                    "simulator",
                    "room"
                  ]
                }
              },
              "instructorOrgUserIds": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "reservationTypes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "orgUser": {
            "type": "object",
            "description": "Member who owns this interest.",
            "additionalProperties": true
          },
          "watchedReservation": {
            "type": [
              "object",
              "null"
            ],
            "description": "Reservation watched when kind is on_reservation.",
            "additionalProperties": true
          },
          "resource": {
            "type": [
              "object",
              "null"
            ],
            "description": "Optional resource filter on the interest row.",
            "additionalProperties": true
          },
          "instructorOrgUser": {
            "type": [
              "object",
              "null"
            ],
            "description": "Optional instructor filter on the interest row.",
            "additionalProperties": true
          },
          "notificationDelivery": {
            "type": "object",
            "description": "Channels currently enabled for slot offers.",
            "properties": {
              "email": {
                "type": "boolean",
                "description": "Email delivery is enabled."
              },
              "push": {
                "type": "boolean",
                "description": "Push delivery is enabled."
              },
              "sms": {
                "type": "boolean",
                "description": "SMS delivery is enabled."
              },
              "anyChannelEnabled": {
                "type": "boolean",
                "description": "At least one delivery channel is enabled."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "SlotOffer": {
        "type": "object",
        "description": "A time-limited offer to claim an available scheduling slot.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "status": {
            "type": "string",
            "description": "Current offer state.",
            "enum": [
              "pending",
              "accepted",
              "declined",
              "expired",
              "withdrawn",
              "superseded"
            ]
          },
          "reclaimableBy": {
            "type": "array",
            "description": "On GET /slot-offers only: org user ids this offer's hold does not block, because they were on, or cancelled, the booking it came from. They can book the held window, which withdraws the offer.",
            "items": {
              "type": "integer"
            }
          },
          "trigger": {
            "type": "string",
            "description": "How the offer began: cancel_recovery from a canceled booking, desk from staff, system from AerScheduler AI idle-window scanner.",
            "enum": [
              "cancel_recovery",
              "desk",
              "system"
            ]
          },
          "purpose": {
            "type": "string",
            "description": "claim books the seat; instructor_confirm asks the instructor to confirm first.",
            "enum": [
              "claim",
              "instructor_confirm"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the offered slot."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "End of the offered slot."
          },
          "reservationType": {
            "type": "string",
            "description": "Reservation type created on acceptance."
          },
          "timeZoneName": {
            "type": "string",
            "description": "IANA time zone used to present the slot."
          },
          "title": {
            "description": "Optional reservation title.",
            "type": [
              "string",
              "null"
            ]
          },
          "holdUntil": {
            "type": "string",
            "format": "date-time",
            "description": "Deadline for accepting this offer."
          },
          "holdsSlot": {
            "type": "boolean",
            "description": "Whether this offer keeps the slot's aircraft and instructor off other schedules until it ends. False for a suggested slot sent to several members at once, where the first to accept books it if it is still free."
          },
          "respondedAt": {
            "format": "date-time",
            "description": "When the offer left pending state.",
            "type": [
              "string",
              "null"
            ]
          },
          "offerGroupId": {
            "type": "string",
            "description": "Stable id shared by each candidate in one recovery chain."
          },
          "offeredTo": {
            "type": "object",
            "description": "Member who may accept this offer.",
            "additionalProperties": true
          },
          "createdBy": {
            "type": [
              "object",
              "null"
            ],
            "description": "Desk member who created a desk offer, if any.",
            "additionalProperties": true
          },
          "standbyInterest": {
            "type": [
              "object",
              "null"
            ],
            "description": "Interest that matched this offer, if any.",
            "additionalProperties": true
          },
          "resource": {
            "type": [
              "object",
              "null"
            ],
            "description": "Resource on the offered slot.",
            "additionalProperties": true
          },
          "instructorOrgUser": {
            "type": [
              "object",
              "null"
            ],
            "description": "Instructor on the offered slot.",
            "additionalProperties": true
          },
          "sourceReservation": {
            "type": [
              "object",
              "null"
            ],
            "description": "Canceled reservation that triggered recovery.",
            "additionalProperties": true
          },
          "resultingReservation": {
            "description": "Reservation created by acceptance.",
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "notificationDelivery": {
            "type": "object",
            "description": "Channels currently enabled for slot offers for the recipient.",
            "properties": {
              "email": {
                "type": "boolean",
                "description": "Email delivery is enabled."
              },
              "push": {
                "type": "boolean",
                "description": "Push delivery is enabled."
              },
              "sms": {
                "type": "boolean",
                "description": "SMS delivery is enabled."
              },
              "anyChannelEnabled": {
                "type": "boolean",
                "description": "At least one delivery channel is enabled."
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "BookingRequest": {
        "type": "object",
        "description": "A member booking pending staff approval before it becomes a reservation.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "status": {
            "type": "string",
            "description": "unverified, pending, approved, rejected, cancelled, or expired."
          },
          "source": {
            "type": "string",
            "description": "internal for members; public_embed for guest requests."
          },
          "title": {
            "description": "Optional reservation title.",
            "type": [
              "string",
              "null"
            ]
          },
          "reservationType": {
            "type": "string",
            "description": "Requested reservation type."
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Requested start."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Requested end."
          },
          "timeZoneName": {
            "type": "string",
            "description": "IANA time zone for the request."
          },
          "notes": {
            "description": "Notes from the requester.",
            "type": [
              "string",
              "null"
            ]
          },
          "personnel": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "answers": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "policySnapshot": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "decidedAt": {
            "format": "date-time",
            "description": "When the request left pending.",
            "type": [
              "string",
              "null"
            ]
          },
          "decisionReason": {
            "description": "Reason shown when rejected.",
            "type": [
              "string",
              "null"
            ]
          },
          "guestName": {
            "description": "Guest name on a public request.",
            "type": [
              "string",
              "null"
            ]
          },
          "guestEmail": {
            "description": "Guest email on a public request.",
            "type": [
              "string",
              "null"
            ]
          },
          "guestPhone": {
            "description": "Guest phone on a public request.",
            "type": [
              "string",
              "null"
            ]
          },
          "requestedBy": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "decidedBy": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "resource": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "instructorOrgUser": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "location": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "resultingReservation": {
            "additionalProperties": true,
            "type": [
              "object",
              "null"
            ]
          },
          "convertedAt": {
            "format": "date-time",
            "description": "When a public guest was invited as a student or renter.",
            "type": [
              "string",
              "null"
            ]
          },
          "convertRole": {
            "description": "student or renter, after conversion.",
            "type": [
              "string",
              "null"
            ]
          },
          "offering": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Offering this request came from, including eligible aircraft and instructors for desk approve overrides."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "OrganizationCalendarVisibility": {
        "type": "object",
        "description": "Per-audience calendar detail levels for one organization.",
        "properties": {
          "guestLevel": {
            "type": "string",
            "description": "What unauthenticated guests see.",
            "enum": [
              "slots_only",
              "blocked_anonymous",
              "own_bookings_named",
              "full"
            ]
          },
          "studentLevel": {
            "type": "string",
            "description": "What signed-in students see.",
            "enum": [
              "slots_only",
              "blocked_anonymous",
              "own_bookings_named",
              "full"
            ]
          },
          "renterLevel": {
            "type": "string",
            "description": "What signed-in renters see.",
            "enum": [
              "slots_only",
              "blocked_anonymous",
              "own_bookings_named",
              "full"
            ]
          },
          "instructorLevel": {
            "type": "string",
            "description": "What signed-in instructors see.",
            "enum": [
              "slots_only",
              "blocked_anonymous",
              "own_bookings_named",
              "full"
            ]
          },
          "memberLevel": {
            "type": "string",
            "description": "What other signed-in members see.",
            "enum": [
              "slots_only",
              "blocked_anonymous",
              "own_bookings_named",
              "full"
            ]
          }
        },
        "additionalProperties": true
      },
      "BookingOffering": {
        "type": "object",
        "description": "A bookable preset over org calendar policy (public guest / discovery flights).",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier, stable for the life of the record."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the record last changed."
          },
          "active": {
            "type": "boolean",
            "description": "Whether the offering accepts new requests."
          },
          "slug": {
            "type": "string",
            "description": "URL-safe identifier unique within the organization."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "description": {
            "description": "Optional description shown on hosted pages.",
            "type": [
              "string",
              "null"
            ]
          },
          "reservationType": {
            "type": "string",
            "description": "Must be guest. Public offerings mint a guest reservation on approval."
          },
          "assignmentMode": {
            "type": "string",
            "description": "desk_assigns or member_picks_resource."
          },
          "minimumNoticeMinutes": {
            "description": "Optional tightening of org minimum notice.",
            "type": [
              "integer",
              "null"
            ]
          },
          "bookingHorizonDays": {
            "description": "Optional tightening of org booking horizon.",
            "type": [
              "integer",
              "null"
            ]
          },
          "fixedReservationMinutes": {
            "description": "Optional fixed slot length override.",
            "type": [
              "integer",
              "null"
            ]
          },
          "bufferBeforeMinutes": {
            "description": "Optional buffer before each slot.",
            "type": [
              "integer",
              "null"
            ]
          },
          "bufferAfterMinutes": {
            "description": "Optional buffer after each slot.",
            "type": [
              "integer",
              "null"
            ]
          },
          "maxReservationMinutes": {
            "description": "Optional max duration cap.",
            "type": [
              "integer",
              "null"
            ]
          },
          "allowResourceChoice": {
            "type": "boolean",
            "description": "Whether the requester may pick an aircraft/sim. A picked tail is held until the request expires, is declined, or is approved."
          },
          "collectionStyle": {
            "type": "string",
            "description": "Default collection style copied onto the reservation at approval.",
            "enum": [
              "close_out",
              "prepaid_fixed"
            ]
          },
          "prepaidAmountCents": {
            "description": "Package price in cents when collectionStyle is prepaid_fixed.",
            "type": [
              "integer",
              "null"
            ]
          },
          "questions": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "location": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "instructors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "BookableSlot": {
        "type": "object",
        "description": "One bookable window projected for the caller's audience.",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Slot start in UTC."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Slot end in UTC."
          },
          "timeZone": {
            "type": "string",
            "description": "IANA time zone used to present the slot."
          },
          "resourceId": {
            "description": "Eligible resource id when the caller may choose one.",
            "type": [
              "integer",
              "null"
            ]
          },
          "resourceLabel": {
            "description": "Resource label when visibility allows naming.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "PublicBookingPage": {
        "type": "object",
        "description": "Public-safe offering page for a guest share link.",
        "properties": {
          "organization": {
            "type": "object",
            "description": "The organization this offering belongs to. embedHosts is public: anyone with the share link can read which sites may iframe the page.",
            "properties": {
              "name": {
                "type": "string",
                "description": "Organization name."
              },
              "slug": {
                "description": "Public booking slug.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "logo": {
                "description": "Organization logo URL.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "timeZone": {
                "description": "Organization IANA time zone.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "privacyUrl": {
                "type": "string",
                "description": "Privacy policy URL shown on the guest form."
              },
              "embedHosts": {
                "type": "array",
                "maxItems": 20,
                "items": {
                  "type": "string",
                  "format": "uri"
                },
                "description": "Origins allowed to iframe this page, like https://www.yoursite.com. Empty means the share link only."
              },
              "brand": {
                "type": "object",
                "description": "Bounded guest-page look. No custom CSS.",
                "properties": {
                  "accentHex": {
                    "description": "Accent #RRGGBB.",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "appearance": {
                    "type": "string",
                    "description": "light, dark, or system."
                  },
                  "density": {
                    "type": "string",
                    "description": "compact or comfortable."
                  },
                  "cornerStyle": {
                    "type": "string",
                    "description": "rounded or sharp."
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": false
          },
          "offering": {
            "type": "object",
            "description": "The offering being requested.",
            "properties": {
              "slug": {
                "type": "string",
                "description": "Offering slug."
              },
              "name": {
                "type": "string",
                "description": "Display name."
              },
              "description": {
                "description": "Description shown to the guest.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "reservationType": {
                "type": "string",
                "description": "Reservation type created on approval."
              },
              "fixedReservationMinutes": {
                "description": "Fixed slot length.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "allowResourceChoice": {
                "type": "boolean",
                "description": "Whether the guest may pick an aircraft."
              },
              "collectionStyle": {
                "type": "string",
                "description": "close_out or prepaid_fixed."
              },
              "prepaidAmountCents": {
                "description": "Package price in cents when prepaid_fixed.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "location": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "timeZone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "WidgetSnapshot": {
        "type": "object",
        "description": "Everything the AerScheduler iOS home-screen widgets render, assembled for one member of one organization.\n\nScoped to the caller throughout: `nextFlights`, `documents` and `account` are theirs, while `fleet` is the whole organization's day, which is what makes an aircraft's status true rather than only true of bookings the caller happens to be on.\n\n`squawks` and `inspections` are OMITTED, not zeroed, for a caller who may not read maintenance. Check `canSeeMaintenance` before concluding anything from their absence: zero open squawks and no permission to see them are different facts.",
        "properties": {
          "v": {
            "type": "integer",
            "description": "Payload version. Bumped only for a breaking reshape."
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the server assembled this."
          },
          "signedIn": {
            "type": "boolean",
            "description": "Always true on a successful response. Present so a client rendering a cached body has the flag it branches on."
          },
          "orgId": {
            "type": "integer",
            "description": "The organization this describes."
          },
          "orgName": {
            "description": "The organization's name.",
            "type": [
              "string",
              "null"
            ]
          },
          "personName": {
            "description": "The caller's name.",
            "type": [
              "string",
              "null"
            ]
          },
          "roles": {
            "type": "array",
            "description": "The roles the caller holds in this organization.",
            "items": {
              "type": "string",
              "enum": [
                "owner",
                "admin",
                "dispatcher",
                "instructor",
                "technician",
                "student",
                "renter"
              ]
            }
          },
          "canSeeMaintenance": {
            "type": "boolean",
            "description": "Whether the caller may read squawks and maintenance reminders. True for an administrator, a dispatcher or a technician."
          },
          "nextFlights": {
            "type": "array",
            "description": "What is on now and what is next, soonest first, at most six.",
            "items": {
              "type": "object",
              "description": "One booking, as a widget row.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "The reservation."
                },
                "title": {
                  "description": "What the booking is called.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "description": "ground, dual, instructor, solo, sim, maintenance. An open set: this is a free string on the reservation, not an enum.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "start": {
                  "format": "date-time",
                  "description": "Scheduled start.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "end": {
                  "format": "date-time",
                  "description": "Scheduled end.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "tail": {
                  "description": "Tail number, when the booking is on an aircraft.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "makeModel": {
                  "description": "Make and model, or Simulator / Classroom when the resource is one of those.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "instructorName": {
                  "description": "Instructors on the booking, comma separated.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "studentName": {
                  "description": "Students on the booking, comma separated.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "lessonTitle": {
                  "description": "Always null today. Which syllabus lesson a booking is for is decided at close-out, so there is nothing to report before the flight.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "locationName": {
                  "description": "Where it departs from.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "cancelled": {
                  "type": "boolean",
                  "description": "Always false here: cancelled bookings are left out of the snapshot entirely."
                },
                "rampStatus": {
                  "type": "string",
                  "description": "Derived from the ramp timestamps.",
                  "enum": [
                    "scheduled",
                    "out",
                    "overdue",
                    "back"
                  ]
                }
              },
              "additionalProperties": true
            }
          },
          "documents": {
            "type": "array",
            "description": "Currencies that carry an expiry, soonest first, at most six.",
            "items": {
              "type": "object",
              "description": "One currency and how long it has left.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "The currency."
                },
                "name": {
                  "type": "string",
                  "description": "The currency type's name."
                },
                "expiresAt": {
                  "format": "date-time",
                  "description": "When it lapses.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "daysRemaining": {
                  "description": "Whole days until it lapses, truncated toward zero. Negative once it has.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "state": {
                  "type": "string",
                  "description": "Computed from the date, not from whether a warning email has been sent.",
                  "enum": [
                    "good",
                    "warning",
                    "expired"
                  ]
                }
              },
              "additionalProperties": true
            }
          },
          "account": {
            "type": "object",
            "description": "What the caller owes, in whichever mode the organization runs.",
            "properties": {
              "mode": {
                "type": "string",
                "description": "`ledger` when the organization runs prepaid balances, `invoices` otherwise.",
                "enum": [
                  "ledger",
                  "invoices"
                ]
              },
              "balanceCents": {
                "description": "Signed. Negative means the member owes the organization. Null outside ledger mode, where a positive balance is not a concept.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "autoRefillOn": {
                "type": "boolean",
                "description": "Automatic top-ups are configured and enabled. Ledger mode only."
              },
              "autoRefillPaused": {
                "type": "boolean",
                "description": "Automatic top-ups are suspended, typically after a declined card. Ledger mode only."
              },
              "unpaidCount": {
                "type": "integer",
                "description": "Invoices billed to this member that are neither paid nor voided."
              },
              "unpaidTotalCents": {
                "type": "integer",
                "description": "What those add up to."
              },
              "oldestUnpaidAt": {
                "format": "date-time",
                "description": "When the oldest of them was raised.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "additionalProperties": true
          },
          "fleet": {
            "type": "array",
            "description": "The organization's aircraft with today's bookings against each, at most twelve.",
            "items": {
              "type": "object",
              "description": "One aircraft, and what today looks like for it.",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "The resource."
                },
                "tail": {
                  "type": "string",
                  "description": "Tail number."
                },
                "makeModel": {
                  "description": "Make and model.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "grounded": {
                  "type": "boolean",
                  "description": "Whether the aircraft is off the line."
                },
                "groundedReason": {
                  "description": "Free text from whoever grounded it. Shown verbatim, never parsed.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "hobbsDeci": {
                  "description": "Hobbs, in tenths of an hour.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "tachDeci": {
                  "description": "Tach, in tenths of an hour.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "dryRateCents": {
                  "description": "Dry rate.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "wetRateCents": {
                  "description": "Wet rate.",
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "status": {
                  "type": "string",
                  "description": "Resolved against the organization's whole day, not only the caller's bookings.",
                  "enum": [
                    "free",
                    "booked",
                    "out",
                    "down"
                  ]
                },
                "statusDetail": {
                  "description": "Already humanised in the organization's time zone: `until 3pm`, `back 11:40am`, or the grounding reason.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "nextFreeStart": {
                  "format": "date-time",
                  "description": "Start of the next bookable window. Null while the aircraft is down.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "nextFreeEnd": {
                  "format": "date-time",
                  "description": "End of that window, or the end of the day when nothing follows. Read the day's end as `nothing else today`, not as `fully booked`.",
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "busy": {
                  "type": "array",
                  "description": "Today's blocks, for the availability strip. The organization's calendar day.",
                  "items": {
                    "type": "object",
                    "description": "One block of time the aircraft is spoken for.",
                    "properties": {
                      "start": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Block start."
                      },
                      "end": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Block end."
                      },
                      "kind": {
                        "type": "string",
                        "description": "What is holding it.",
                        "enum": [
                          "booking",
                          "maintenance"
                        ]
                      }
                    },
                    "additionalProperties": true
                  }
                }
              },
              "additionalProperties": true
            }
          },
          "training": {
            "type": "object",
            "description": "Where the caller stands on their course. Omitted unless they have a live enrollment.",
            "properties": {
              "courseName": {
                "description": "The course.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "stageName": {
                "description": "The first stage still holding an unfinished lesson. Null once every lesson is done.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "lessonsComplete": {
                "type": "integer",
                "description": "Distinct lessons passed, signed where the syllabus asks for it, and not superseded."
              },
              "lessonsTotal": {
                "type": "integer",
                "description": "Lessons in the enrolled version. Zero is a real state and must not render as either 0% or NaN."
              },
              "standings": {
                "type": "array",
                "description": "Standing against each requirement, in syllabus order, at most six.",
                "items": {
                  "type": "object",
                  "description": "One requirement, and what the training ledger credits toward it.",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine key, e.g. `total_flight`, `solo_xc`."
                    },
                    "label": {
                      "type": "string",
                      "description": "What the requirement is called."
                    },
                    "requiredDeci": {
                      "type": "integer",
                      "description": "What the syllabus asks for, in tenths of an hour. Zero on a requirement written as a count of events rather than as hours."
                    },
                    "creditedDeci": {
                      "type": "integer",
                      "description": "What the ledger credits, in tenths of an hour, after any ceiling."
                    },
                    "met": {
                      "type": "boolean",
                      "description": "Whether the requirement is satisfied."
                    }
                  },
                  "additionalProperties": true
                }
              }
            },
            "additionalProperties": true
          },
          "instructor": {
            "type": "object",
            "description": "The caller's teaching day. Omitted unless they hold the instructor role.",
            "properties": {
              "flights": {
                "type": "array",
                "description": "Today's bookings, soonest first, at most six.",
                "items": {
                  "type": "object",
                  "description": "One booking, as a widget row.",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "The reservation."
                    },
                    "title": {
                      "description": "What the booking is called.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "type": {
                      "description": "ground, dual, instructor, solo, sim, maintenance. An open set: this is a free string on the reservation, not an enum.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "start": {
                      "format": "date-time",
                      "description": "Scheduled start.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "end": {
                      "format": "date-time",
                      "description": "Scheduled end.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "tail": {
                      "description": "Tail number, when the booking is on an aircraft.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "makeModel": {
                      "description": "Make and model, or Simulator / Classroom when the resource is one of those.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "instructorName": {
                      "description": "Instructors on the booking, comma separated.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "studentName": {
                      "description": "Students on the booking, comma separated.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "lessonTitle": {
                      "description": "Always null today. Which syllabus lesson a booking is for is decided at close-out, so there is nothing to report before the flight.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "locationName": {
                      "description": "Where it departs from.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "cancelled": {
                      "type": "boolean",
                      "description": "Always false here: cancelled bookings are left out of the snapshot entirely."
                    },
                    "rampStatus": {
                      "type": "string",
                      "description": "Derived from the ramp timestamps.",
                      "enum": [
                        "scheduled",
                        "out",
                        "overdue",
                        "back"
                      ]
                    }
                  },
                  "additionalProperties": true
                }
              },
              "ungradedCount": {
                "type": "integer",
                "description": "Always zero. Answering it properly needs a per-booking curriculum lookup, which this endpoint will not do on a widget timer."
              },
              "oldestUngradedDays": {
                "description": "Always null, for the same reason as `ungradedCount`.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "notClosedOutCount": {
                "type": "integer",
                "description": "Bookings that have ended with nobody signed off on them."
              }
            },
            "additionalProperties": true
          },
          "squawks": {
            "type": "object",
            "description": "Open squawks. OMITTED when `canSeeMaintenance` is false.",
            "properties": {
              "open": {
                "type": "integer",
                "description": "Open squawks across the fleet."
              },
              "grounding": {
                "type": "integer",
                "description": "How many of those take an aircraft off the line."
              },
              "items": {
                "type": "array",
                "description": "Most recently reported first, at most six.",
                "items": {
                  "type": "object",
                  "description": "One open squawk.",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "The squawk."
                    },
                    "title": {
                      "type": "string",
                      "description": "What was written on it."
                    },
                    "tail": {
                      "description": "The aircraft it is against.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "priority": {
                      "type": "string",
                      "description": "Flattened to the two a widget colours by. High and low arrive as `normal`.",
                      "enum": [
                        "grounding",
                        "normal"
                      ]
                    },
                    "reportedAt": {
                      "format": "date-time",
                      "description": "When it was reported, falling back to when the row was written.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "lane": {
                      "type": "string",
                      "description": "Derived from the timestamps. A squawk has no assignee and no status column, so these are the only lanes the data supports.",
                      "enum": [
                        "new",
                        "verified",
                        "resolved"
                      ]
                    }
                  },
                  "additionalProperties": true
                }
              }
            },
            "additionalProperties": true
          },
          "inspections": {
            "type": "object",
            "description": "Inspections coming due or already past. OMITTED when `canSeeMaintenance` is false.",
            "properties": {
              "overdue": {
                "type": "integer",
                "description": "How many are past due."
              },
              "dueSoon": {
                "type": "integer",
                "description": "How many are inside their template's warning band."
              },
              "items": {
                "type": "array",
                "description": "Most urgent first, at most six.",
                "items": {
                  "type": "object",
                  "description": "One inspection and its countdown.",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "The maintenance reminder."
                    },
                    "tail": {
                      "description": "The aircraft it is against.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "name": {
                      "type": "string",
                      "description": "What the inspection is called."
                    },
                    "status": {
                      "type": "string",
                      "description": "The server's computed band.",
                      "enum": [
                        "overdue",
                        "dueSoon",
                        "ok",
                        "resolved"
                      ]
                    },
                    "hoursRemainingDeci": {
                      "description": "Tenths of an hour left on the meter. Negative when overdue. Null on a calendar interval.",
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "daysRemaining": {
                      "description": "Days until due. Negative when overdue. Null on an hour interval.",
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "dueAt": {
                      "format": "date-time",
                      "description": "When it comes due. Null on an hour interval, where nobody can know the date.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "progress": {
                      "description": "0 through the interval to 1 at due, above 1 when overdue.",
                      "type": [
                        "number",
                        "null",
                        "null"
                      ]
                    }
                  },
                  "additionalProperties": true
                }
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "Error": {
        "type": "object",
        "description": "Every error response has this shape.",
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable explanation, safe to show a user."
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds to wait before retrying. Present on 429 only."
          }
        },
        "required": [
          "message"
        ]
      }
    }
  }
}