{
  "openapi": "3.1.0",
  "info": {
    "title": "MSP Align API",
    "version": "1.0",
    "description": "Read and change planning data in MSP Align. JSON in and out.\n\n**Authentication**: send the key from Settings → API as `Authorization: Bearer msa_…` (or `X-API-Key`). Each key has scopes (area:read / area:write) and can be limited to certain clients; everything else answers 404.\n\n**Responses**: one item is `{\"data\": {...}}`; lists are `{\"data\": [...], \"meta\": {\"page\", \"per_page\", \"total\", \"has_more\"}}` with `?page=` and `?per_page=` (max 200). Errors are `{\"error\": {\"code\", \"message\", \"fields\"}, \"request_id\"}`.\n\n**Changes**: PATCH changes only the fields you send; null clears a field. Unknown fields are refused (422) so typos don't pass silently. Every change is written to the audit log with the key's name.\n\n**Rate limit**: per key per minute (X-RateLimit-* headers; 429 with Retry-After). **Retries**: send an `Idempotency-Key` header on POST.\n\n**Dates**: dates are YYYY-MM-DD; times are ISO 8601 with offset. Quarters accept `2027-Q1` or any date in the quarter and come back as the quarter's first day (Q = calendar quarter)."
  },
  "servers": [
    {
      "url": "https://align.example.com",
      "description": "Your MSP Align server"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "tags": [
    {
      "name": "Meta"
    },
    {
      "name": "Clients"
    },
    {
      "name": "Contacts"
    },
    {
      "name": "Devices"
    },
    {
      "name": "Projects"
    },
    {
      "name": "Budget"
    },
    {
      "name": "Licensing"
    },
    {
      "name": "Vendors"
    },
    {
      "name": "Meetings"
    },
    {
      "name": "Compliance"
    },
    {
      "name": "Health"
    },
    {
      "name": "Alignment"
    },
    {
      "name": "Backups"
    },
    {
      "name": "Service levels"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getKey",
        "tags": [
          "Meta"
        ],
        "summary": "About this key",
        "description": "The key's name, scopes, client limit, expiry and rate limit. A cheap way to test a key.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Key"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "Clients"
        ],
        "summary": "List clients",
        "description": "Requires the `clients:read` scope.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name contains.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_excluded",
            "in": "query",
            "required": false,
            "description": "Include clients taken out of planning.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "clients:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}": {
      "get": {
        "operationId": "getClient",
        "tags": [
          "Clients"
        ],
        "summary": "Get a client with its health summary",
        "description": "The summary has a section for each area the key can read: devices, projects, next meeting, backups, compliance and service levels.\n\nRequires the `clients:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClientDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "clients:read"
            ]
          }
        ]
      }
    },
    "/api/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "Requires the `contacts:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "description": "primary, billing, technical, important, decision_maker or meeting_invitee.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name or email contains.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_archived",
            "in": "query",
            "required": false,
            "description": "Include archived contacts.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "contacts:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/contacts": {
      "get": {
        "operationId": "listClientsContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List a client's contacts",
        "description": "Requires the `contacts:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "description": "Filter by role.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name or email contains.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "contacts:read"
            ]
          }
        ]
      }
    },
    "/api/v1/contacts/{id}": {
      "get": {
        "operationId": "getContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Get a contact",
        "description": "Requires the `contacts:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "contacts:read"
            ]
          }
        ]
      }
    },
    "/api/v1/devices": {
      "get": {
        "operationId": "listDevices",
        "tags": [
          "Devices"
        ],
        "summary": "List devices with lifecycle status",
        "description": "Requires the `devices:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Lifecycle status: replace, os_eos, plan, deferred, os_soon, warranty_expired, warranty_soon, ok, excluded, virtual.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "attention",
            "in": "query",
            "required": false,
            "description": "Only devices that need attention (true) or don't (false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Device type, e.g. Server, Laptop.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "class",
            "in": "query",
            "required": false,
            "description": "Category: desktop, laptop, server, network, printer, storage, power, other.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "virtual",
            "in": "query",
            "required": false,
            "description": "Only virtual (true) or physical (false).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name, serial, model or last user contains.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "devices:read"
            ]
          }
        ]
      }
    },
    "/api/v1/devices/{id}": {
      "get": {
        "operationId": "getDevice",
        "tags": [
          "Devices"
        ],
        "summary": "Get a device",
        "description": "Requires the `devices:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Device"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "devices:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateDevice",
        "tags": [
          "Devices"
        ],
        "summary": "Change a device's lifecycle details",
        "description": "Same as editing the device page: dates, lifespan and cost override the synced values and the policy defaults; the change goes to the PSA when two-way sync is on. Send null to clear an override.\n\nRequires the `devices:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Device"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "devices:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "purchase_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Purchase / in-service date. Overrides what the RMM or PSA says. null = use the synced date."
                  },
                  "warranty_end": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Warranty end. null = use the warranty lookup or PSA date."
                  },
                  "lifespan_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 29,
                    "description": "Lifespan for this device. null = the policy default for its type."
                  },
                  "replacement_cost": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000000,
                    "description": "Replacement cost. null = the policy default for its type."
                  },
                  "replace_quarter": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "examples": [
                      "2027-Q1",
                      "2027-01-01"
                    ],
                    "description": "Plan the replacement in this quarter instead of at end of life (2027-Q2 or any date in it). null = follow end of life."
                  },
                  "replace_note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "description": "Why the replacement was moved (kept only with replace_quarter)."
                  },
                  "excluded": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Leave the device out of planning, budgets and reports."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000,
                    "description": "Align notes."
                  },
                  "asset_tag": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 64,
                    "description": "The client's own asset tag (2.21.0). null = none. Not unique: the same tag on two devices is allowed."
                  },
                  "device_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "Desktop",
                      "VDI / virtual desktop",
                      "Laptop",
                      "Server",
                      "Hypervisor host",
                      "Virtual server",
                      "Firewall",
                      "Router",
                      "Switch",
                      "Access point",
                      "Printer",
                      "NAS / Storage",
                      "UPS",
                      "Monitor",
                      "Phone",
                      "Camera / NVR",
                      "Other",
                      "Unassigned"
                    ],
                    "description": "Device type (sets its lifecycle category). null = the type the RMM reports (not for devices added in Align or the PSA, whose type is their own)."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Requires the `projects:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "proposed, approved, scheduled, done or declined.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Project category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "quarter",
            "in": "query",
            "required": false,
            "description": "Planned in this quarter (2027-Q1 or any date in it).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "projects:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createProject",
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "description": "Requires the `projects:write` scope.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "projects:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Client the project belongs to (can't be changed later)."
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Project name."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "hardware",
                      "infrastructure",
                      "security",
                      "compliance",
                      "software",
                      "cloud",
                      "backup",
                      "project",
                      "training"
                    ],
                    "description": "Category (default \"project\")."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Plain-language description the client will read."
                  },
                  "target_quarter": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "examples": [
                      "2027-Q1",
                      "2027-01-01"
                    ],
                    "description": "Planned quarter (2027-Q1 or any date in it; returned as the quarter's first day). null = backlog."
                  },
                  "cost": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "One-time cost."
                  },
                  "recurring_monthly": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000000,
                    "description": "Added monthly cost once done."
                  },
                  "priority": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "critical",
                      "high",
                      "medium",
                      "low"
                    ],
                    "description": "Priority (default \"medium\")."
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "proposed",
                      "approved",
                      "scheduled",
                      "done",
                      "declined"
                    ],
                    "description": "Status (default \"proposed\"). Approved and scheduled projects roll into the budget."
                  },
                  "alignment_standard_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "The alignment standard this project fixes (GET /alignment/standards)."
                  }
                },
                "required": [
                  "client_id",
                  "title"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{id}": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "Projects"
        ],
        "summary": "Get a project",
        "description": "Requires the `projects:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "projects:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateProject",
        "tags": [
          "Projects"
        ],
        "summary": "Change a project",
        "description": "Requires the `projects:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "projects:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Project name."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "hardware",
                      "infrastructure",
                      "security",
                      "compliance",
                      "software",
                      "cloud",
                      "backup",
                      "project",
                      "training"
                    ],
                    "description": "Category (default \"project\")."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Plain-language description the client will read."
                  },
                  "target_quarter": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "examples": [
                      "2027-Q1",
                      "2027-01-01"
                    ],
                    "description": "Planned quarter (2027-Q1 or any date in it; returned as the quarter's first day). null = backlog."
                  },
                  "cost": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "One-time cost."
                  },
                  "recurring_monthly": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000000,
                    "description": "Added monthly cost once done."
                  },
                  "priority": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "critical",
                      "high",
                      "medium",
                      "low"
                    ],
                    "description": "Priority (default \"medium\")."
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "proposed",
                      "approved",
                      "scheduled",
                      "done",
                      "declined"
                    ],
                    "description": "Status (default \"proposed\"). Approved and scheduled projects roll into the budget."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteProject",
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Requires the `projects:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "projects:write"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/budget": {
      "get": {
        "operationId": "getClientsBudget",
        "tags": [
          "Budget"
        ],
        "summary": "Get a client's three-year technology budget",
        "description": "Everything the budget page shows: totals per year and quarter by category, recurring monthly run rate, each line (licensing, hardware, projects, managed services and your budget lines) and upcoming contract dates.\n\nRequires the `budget:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "description": "Which plan year the selected_year block shows: 0 (default), 1 or 2.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BudgetSummary"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:read"
            ]
          }
        ]
      }
    },
    "/api/v1/budget-lines": {
      "get": {
        "operationId": "listBudgetLines",
        "tags": [
          "Budget"
        ],
        "summary": "List budget lines",
        "description": "Requires the `budget:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Budget category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BudgetLine"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createBudgetLine",
        "tags": [
          "Budget"
        ],
        "summary": "Create a budget line",
        "description": "Requires the `budget:write` scope.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BudgetLine"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Client (can't be changed later)."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "What the cost is for."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "managed",
                      "licensing",
                      "hardware",
                      "projects",
                      "connectivity",
                      "telecom",
                      "cloud",
                      "other"
                    ],
                    "description": "Budget category (default \"other\"). A \"managed\" line replaces the managed-services estimate from PSA invoices."
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 190
                  },
                  "amount": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Amount per billing period."
                  },
                  "frequency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual",
                      "one_time"
                    ],
                    "description": "How often it's billed (default \"monthly\")."
                  },
                  "start_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "First billing date (null = already running)."
                  },
                  "end_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Last billing date (null = ongoing)."
                  },
                  "contract_term_months": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 240
                  },
                  "contract_end": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from start_date + term when left out."
                  },
                  "notice_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 730
                  },
                  "renegotiate_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Last day to give notice. Worked out from contract_end - notice_days when left out."
                  },
                  "auto_renew": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  }
                },
                "required": [
                  "client_id",
                  "name",
                  "amount"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/budget-lines/{id}": {
      "get": {
        "operationId": "getBudgetLine",
        "tags": [
          "Budget"
        ],
        "summary": "Get a budget line",
        "description": "Requires the `budget:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BudgetLine"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateBudgetLine",
        "tags": [
          "Budget"
        ],
        "summary": "Change a budget line",
        "description": "Requires the `budget:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BudgetLine"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "What the cost is for."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "managed",
                      "licensing",
                      "hardware",
                      "projects",
                      "connectivity",
                      "telecom",
                      "cloud",
                      "other"
                    ],
                    "description": "Budget category (default \"other\"). A \"managed\" line replaces the managed-services estimate from PSA invoices."
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 190
                  },
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "Amount per billing period."
                  },
                  "frequency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual",
                      "one_time"
                    ],
                    "description": "How often it's billed (default \"monthly\")."
                  },
                  "start_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "First billing date (null = already running)."
                  },
                  "end_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Last billing date (null = ongoing)."
                  },
                  "contract_term_months": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 240
                  },
                  "contract_end": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from start_date + term when left out."
                  },
                  "notice_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 730
                  },
                  "renegotiate_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Last day to give notice. Worked out from contract_end - notice_days when left out."
                  },
                  "auto_renew": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteBudgetLine",
        "tags": [
          "Budget"
        ],
        "summary": "Delete a budget line",
        "description": "Requires the `budget:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "budget:write"
            ]
          }
        ]
      }
    },
    "/api/v1/licenses": {
      "get": {
        "operationId": "listLicenses",
        "tags": [
          "Licensing"
        ],
        "summary": "List licenses",
        "description": "Requires the `licenses:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "License category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unpriced",
            "in": "query",
            "required": false,
            "description": "Only licenses without a price.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "renewing_within_days",
            "in": "query",
            "required": false,
            "description": "Expiry, contract end or renegotiate-by date within this many days.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "include_retired",
            "in": "query",
            "required": false,
            "description": "Include retired licenses.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/License"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "licenses:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createLicense",
        "tags": [
          "Licensing"
        ],
        "summary": "Add a license",
        "description": "For licenses you track only in Align. Licenses from the PSA arrive with the sync.\n\nRequires the `licenses:write` scope.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/License"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "licenses:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Client (can't be changed later)."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Product name (read-only for licenses synced from the PSA)."
                  },
                  "version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "software_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "license_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "user",
                      "device",
                      "site",
                      "other"
                    ]
                  },
                  "seats": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000,
                    "description": "Seats purchased (read-only for licenses synced from the PSA)."
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 190
                  },
                  "purchase_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "expire_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Renewal / expiry date (read-only for licenses synced from the PSA)."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "productivity",
                      "security",
                      "backup",
                      "lob",
                      "infrastructure",
                      "communication",
                      "rmm",
                      "other"
                    ]
                  },
                  "pricing": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "per_seat",
                      "flat"
                    ],
                    "description": "per_seat: unit_price × seats; flat: unit_price is the whole charge."
                  },
                  "unit_price": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000000,
                    "description": "Price per seat (or flat price) per billing period. null = not priced yet."
                  },
                  "billing_cycle": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual",
                      "one_time"
                    ]
                  },
                  "seats_used": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000
                  },
                  "auto_renew": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "contract_start": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "contract_term_months": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 240
                  },
                  "contract_end": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from contract_start (or purchase_date) + term when left out."
                  },
                  "notice_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 730
                  },
                  "renegotiate_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from contract_end - notice_days when left out."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000,
                    "description": "Align notes."
                  }
                },
                "required": [
                  "client_id",
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/licenses/{id}": {
      "get": {
        "operationId": "getLicense",
        "tags": [
          "Licensing"
        ],
        "summary": "Get a license",
        "description": "Requires the `licenses:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/License"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "licenses:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateLicense",
        "tags": [
          "Licensing"
        ],
        "summary": "Change a license",
        "description": "For licenses synced from the PSA, name, version, type, seats, vendor and dates are managed there and refused here.\n\nRequires the `licenses:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/License"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "licenses:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Product name (read-only for licenses synced from the PSA)."
                  },
                  "version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "software_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "license_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "user",
                      "device",
                      "site",
                      "other"
                    ]
                  },
                  "seats": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000,
                    "description": "Seats purchased (read-only for licenses synced from the PSA)."
                  },
                  "vendor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 190
                  },
                  "purchase_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "expire_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Renewal / expiry date (read-only for licenses synced from the PSA)."
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "productivity",
                      "security",
                      "backup",
                      "lob",
                      "infrastructure",
                      "communication",
                      "rmm",
                      "other"
                    ]
                  },
                  "pricing": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "per_seat",
                      "flat"
                    ],
                    "description": "per_seat: unit_price × seats; flat: unit_price is the whole charge."
                  },
                  "unit_price": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000000,
                    "description": "Price per seat (or flat price) per billing period. null = not priced yet."
                  },
                  "billing_cycle": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "monthly",
                      "quarterly",
                      "annual",
                      "one_time"
                    ]
                  },
                  "seats_used": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000
                  },
                  "auto_renew": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "contract_start": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "contract_term_months": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 240
                  },
                  "contract_end": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from contract_start (or purchase_date) + term when left out."
                  },
                  "notice_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 730
                  },
                  "renegotiate_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Worked out from contract_end - notice_days when left out."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000,
                    "description": "Align notes."
                  },
                  "retired": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Retire (true) or restore (false). Retired licenses don't count toward totals."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteLicense",
        "tags": [
          "Licensing"
        ],
        "summary": "Delete a license you added",
        "description": "Requires the `licenses:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "licenses:write"
            ]
          }
        ]
      }
    },
    "/api/v1/vendors": {
      "get": {
        "operationId": "listVendors",
        "tags": [
          "Vendors"
        ],
        "summary": "List client vendors",
        "description": "Each client's vendors as its Vendors page shows them: a field left blank on the vendor takes its template's value (from_template lists which).\n\nRequires the `vendors:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Vendor category (its own, else its template's).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_retired",
            "in": "query",
            "required": false,
            "description": "Include retired vendors.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Vendor"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "vendors:read"
            ]
          }
        ]
      }
    },
    "/api/v1/vendors/{id}": {
      "get": {
        "operationId": "getVendor",
        "tags": [
          "Vendors"
        ],
        "summary": "Get a client vendor",
        "description": "Requires the `vendors:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Vendor"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "vendors:read"
            ]
          }
        ]
      }
    },
    "/api/v1/vendor-templates": {
      "get": {
        "operationId": "getVendorTemplates",
        "tags": [
          "Vendors"
        ],
        "summary": "List vendor templates",
        "description": "The shared vendor details entered once, with how many clients use each. Not paginated.\n\nRequires the `vendors:read` scope.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VendorTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "vendors:read"
            ]
          }
        ]
      }
    },
    "/api/v1/meetings": {
      "get": {
        "operationId": "listMeetings",
        "tags": [
          "Meetings"
        ],
        "summary": "List meetings",
        "description": "Requires the `meetings:read` scope.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Only this client.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Starting on or after (date or date-time).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Starting on or before.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "scheduled, completed or cancelled.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Meeting type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only items changed since this date/time (ISO 8601). Use it to sync incrementally.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Meeting"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "meetings:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createMeeting",
        "tags": [
          "Meetings"
        ],
        "summary": "Schedule a meeting",
        "description": "Requires the `meetings:write` scope.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Meeting"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "meetings:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Client (null = internal meeting; needs a key for all clients)."
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "description": "Title (default: the meeting type's name)."
                  },
                  "type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "abr",
                      "qbr",
                      "tbr",
                      "strategy",
                      "onboarding",
                      "compliance",
                      "project",
                      "internal",
                      "other"
                    ],
                    "description": "Meeting type (default \"other\")."
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Start, ISO 8601 (e.g. 2026-10-14T09:00:00-07:00). Without an offset the server's time zone is used."
                  },
                  "duration_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 15,
                    "maximum": 600,
                    "description": "Length (default from Settings → Planning)."
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "video_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri",
                    "description": "Teams / Meet / Zoom link."
                  },
                  "attendees": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "List of \"email\" or \"Name <email>\"."
                  },
                  "agenda": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "owner_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Staff user (tech or admin) who owns / organizes it (default: the key's creator). Invitations can go out from this person's mailbox, so only keys for all clients may set it."
                  },
                  "send_invites": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Email calendar invitations (or updates) to the attendees. Default false. Needs a key for all clients."
                  }
                },
                "required": [
                  "starts_at"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/meetings/{id}": {
      "get": {
        "operationId": "getMeeting",
        "tags": [
          "Meetings"
        ],
        "summary": "Get a meeting",
        "description": "Requires the `meetings:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Meeting"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "meetings:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateMeeting",
        "tags": [
          "Meetings"
        ],
        "summary": "Change, complete or cancel a meeting",
        "description": "Requires the `meetings:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Meeting"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "meetings:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Client (null = internal meeting; needs a key for all clients)."
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "description": "Title (default: the meeting type's name)."
                  },
                  "type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "abr",
                      "qbr",
                      "tbr",
                      "strategy",
                      "onboarding",
                      "compliance",
                      "project",
                      "internal",
                      "other"
                    ],
                    "description": "Meeting type (default \"other\")."
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Start, ISO 8601 (e.g. 2026-10-14T09:00:00-07:00). Without an offset the server's time zone is used."
                  },
                  "duration_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 15,
                    "maximum": 600,
                    "description": "Length (default from Settings → Planning)."
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "video_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri",
                    "description": "Teams / Meet / Zoom link."
                  },
                  "attendees": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "List of \"email\" or \"Name <email>\"."
                  },
                  "agenda": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "owner_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Staff user (tech or admin) who owns / organizes it (default: the key's creator). Invitations can go out from this person's mailbox, so only keys for all clients may set it."
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "scheduled",
                      "completed",
                      "cancelled"
                    ],
                    "description": "Mark completed or cancelled (cancelling sends cancellations if invitations went out), or scheduled to reopen."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50000,
                    "description": "Meeting notes."
                  },
                  "send_invites": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Email calendar invitations (or updates) to the attendees. Default false. Needs a key for all clients."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteMeeting",
        "tags": [
          "Meetings"
        ],
        "summary": "Delete a meeting",
        "description": "Attendees get a cancellation first if invitations had gone out.\n\nRequires the `meetings:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "meetings:write"
            ]
          }
        ]
      }
    },
    "/api/v1/compliance/frameworks": {
      "get": {
        "operationId": "listComplianceFrameworks",
        "tags": [
          "Compliance"
        ],
        "summary": "List frameworks",
        "description": "Requires the `compliance:read` scope.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Framework"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/compliance": {
      "get": {
        "operationId": "listClientsCompliance",
        "tags": [
          "Compliance"
        ],
        "summary": "List a client's frameworks with scores",
        "description": "Requires the `compliance:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Assessment"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createClientsCompliance",
        "tags": [
          "Compliance"
        ],
        "summary": "Assign a framework to a client",
        "description": "Requires the `compliance:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assessment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "framework_id": {
                    "type": "integer",
                    "description": "Framework to assign."
                  },
                  "next_review": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "Next review (default one year)."
                  }
                },
                "required": [
                  "framework_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/compliance/{framework}": {
      "patch": {
        "operationId": "updateClientsCompliance",
        "tags": [
          "Compliance"
        ],
        "summary": "Set review dates",
        "description": "Requires the `compliance:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "framework",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Assessment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "next_review": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "last_reviewed": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteClientsCompliance",
        "tags": [
          "Compliance"
        ],
        "summary": "Remove a framework from a client",
        "description": "Answers are kept and come back if the framework is assigned again.\n\nRequires the `compliance:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "framework",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:write"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/compliance/{framework}/controls": {
      "get": {
        "operationId": "listClientsComplianceControls",
        "tags": [
          "Compliance"
        ],
        "summary": "List controls with the client's status",
        "description": "Requires the `compliance:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "framework",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "not_assessed, met, partial, not_met or na.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Control"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:read"
            ]
          }
        ]
      },
      "patch": {
        "operationId": "updateManyClientsComplianceControls",
        "tags": [
          "Compliance"
        ],
        "summary": "Update many controls",
        "description": "Requires the `compliance:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "framework",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BulkResult"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "controls": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Up to 500 items: {\"id\": <control id>, \"status\": \"met\", \"notes\": \"...\"}. All or nothing."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/compliance/{framework}/controls/{control}": {
      "patch": {
        "operationId": "updateClientsComplianceControl",
        "tags": [
          "Compliance"
        ],
        "summary": "Update one control",
        "description": "Requires the `compliance:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "framework",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "control",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Control"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "compliance:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "not_assessed",
                      "met",
                      "partial",
                      "not_met",
                      "na"
                    ],
                    "description": "Assessment result."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  },
                  "evidence": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000,
                    "description": "Where the evidence is (link, file name, ticket)."
                  },
                  "owner": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 190,
                    "description": "Who is responsible."
                  },
                  "due_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "document_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Linked Align document (must belong to the client)."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/changes": {
      "get": {
        "operationId": "getClientsChanges",
        "tags": [
          "Clients"
        ],
        "summary": "What changed since the last review",
        "description": "Projects finished, devices replaced, added and removed, warranties, end of life and OS support that ran out, alignment and compliance changes, licenses and tickets since the starting point. Each part is included only when the key can read its area (tickets need service:read). Then-figures come from the snapshot saved when the review was completed (since 2.3.0); without one they are null.\n\nRequires the `clients:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "m<meeting id> (one of the client's completed business reviews) or YYYY-MM-DD; default: the newest completed review.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Changes"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "clients:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/health": {
      "get": {
        "operationId": "getClientsHealth",
        "tags": [
          "Health"
        ],
        "summary": "Get a client's health score",
        "description": "One score from 0 to 100 made from lifecycle, backups, compliance, service levels and alignment, each scored 0 to 100 and weighted as set in Settings → Planning & lifecycle. An area with no data is left out. Each area's details (what pulls it down) are included only when the key can read that area; otherwise details is null. Worked out now, and stored as today's entry in the history.\n\nRequires the `health:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Health"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "health:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/health/history": {
      "get": {
        "operationId": "listClientsHealthHistory",
        "tags": [
          "Health"
        ],
        "summary": "Get a client's health history",
        "description": "One entry per day that was recorded (once a day, and whenever the client is opened), newest first. Scores are worked out with the weights in use now.\n\nRequires the `health:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "How many days back, 1-1100 (default 90).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HealthDay"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "health:read"
            ]
          }
        ]
      }
    },
    "/api/v1/alignment/standards": {
      "get": {
        "operationId": "listAlignmentStandards",
        "tags": [
          "Alignment"
        ],
        "summary": "List the standards",
        "description": "The library alignment reviews measure clients against. Admins edit it in the web app (Libraries → Alignment standards).\n\nRequires the `alignment:read` scope.",
        "parameters": [
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "description": "Also standards switched off (kept for old reviews).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Standard"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/alignment": {
      "get": {
        "operationId": "getClientsAlignment",
        "tags": [
          "Alignment"
        ],
        "summary": "Get a client's alignment",
        "description": "The latest finished review's score and band, the change since the review before, the gaps (most important first, each with its roadmap project if one was made and the compliance controls it helps with) and the open draft.\n\nRequires the `alignment:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Alignment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/alignment/reviews": {
      "get": {
        "operationId": "listClientsAlignmentReviews",
        "tags": [
          "Alignment"
        ],
        "summary": "List a client's reviews",
        "description": "Requires the `alignment:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AlignmentReview"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createClientsAlignmentReview",
        "tags": [
          "Alignment"
        ],
        "summary": "Start a review",
        "description": "Starts from the last finished review's answers. If a draft is already open it is returned instead (200). Send an empty body.\n\nRequires the `alignment:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AlignmentReview"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:write"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/alignment/reviews/{review}": {
      "get": {
        "operationId": "getClientsAlignmentReview",
        "tags": [
          "Alignment"
        ],
        "summary": "Get a review with its answers",
        "description": "Requires the `alignment:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "review",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AlignmentReview"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:read"
            ]
          }
        ]
      },
      "delete": {
        "operationId": "deleteClientsAlignmentReview",
        "tags": [
          "Alignment"
        ],
        "summary": "Discard a draft",
        "description": "Finished reviews are kept: 409.\n\nRequires the `alignment:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "review",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:write"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/alignment/reviews/{review}/answers": {
      "patch": {
        "operationId": "updateManyClientsAlignmentReviewsAnswers",
        "tags": [
          "Alignment"
        ],
        "summary": "Answer standards in a draft",
        "description": "Requires the `alignment:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "review",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AlignmentAnswersResult"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "answers": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "Up to 500 items: {\"standard_id\": 12, \"answer\": \"aligned\", \"note\": \"...\"}. answer is aligned, misaligned or na; a field left out stays as it is. All or nothing; 409 when the review is finished."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/alignment/reviews/{review}/finish": {
      "post": {
        "operationId": "createClientsAlignmentReviewsFinish",
        "tags": [
          "Alignment"
        ],
        "summary": "Finish a draft",
        "description": "Stores the score (weighted by priority; N/A and unanswered standards left out). 409 when it is already finished.\n\nRequires the `alignment:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "review",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AlignmentReview"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "alignment:write"
            ]
          }
        ]
      }
    },
    "/api/v1/backups": {
      "get": {
        "operationId": "listBackups",
        "tags": [
          "Backups"
        ],
        "summary": "Backup health for every client",
        "description": "Requires the `backups:read` scope.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BackupSummary"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/backups": {
      "get": {
        "operationId": "getClientsBackups",
        "tags": [
          "Backups"
        ],
        "summary": "Get a client's backups",
        "description": "Stats, 30-day history, jobs (with the backup product's messages), protected machines, Microsoft 365 and servers with no backup. Includes machines backed up on your own server.\n\nRequires the `backups:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClientBackups"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:read"
            ]
          }
        ]
      }
    },
    "/api/v1/clients/{id}/backup-exemptions": {
      "get": {
        "operationId": "listClientsBackupExemptions",
        "tags": [
          "Backups"
        ],
        "summary": "List items marked \"backup not required\"",
        "description": "Requires the `backups:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Exemption"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:read"
            ]
          }
        ]
      },
      "post": {
        "operationId": "createClientsBackupExemption",
        "tags": [
          "Backups"
        ],
        "summary": "Mark an item \"backup not required\"",
        "description": "Requires the `backups:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Send a unique value to make retries safe: the same key within 24 hours returns the first response instead of creating a duplicate."
          }
        ],
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Exemption"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "device",
                      "workload",
                      "m365"
                    ]
                  },
                  "device_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "For kind=device."
                  },
                  "item_uid": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "For kind=workload or m365."
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 255
                  }
                },
                "required": [
                  "kind",
                  "reason"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/backup-exemptions/{exemption}": {
      "delete": {
        "operationId": "deleteClientsBackupExemption",
        "tags": [
          "Backups"
        ],
        "summary": "Remove a \"not required\" mark",
        "description": "Requires the `backups:write` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "exemption",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done (no content)."
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:write"
            ]
          }
        ]
      }
    },
    "/api/v1/backups/hosted": {
      "get": {
        "operationId": "listBackupsHosted",
        "tags": [
          "Backups"
        ],
        "summary": "Machines and jobs on your own backup server",
        "description": "meta.jobs lists the jobs. Needs a key for all clients.\n\nRequires the `backups:read` scope.",
        "parameters": [
          {
            "name": "show",
            "in": "query",
            "required": false,
            "description": "unmatched, sorted, ours or all (default).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Items per page, 1-200 (default 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HostedMachine"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:read"
            ]
          }
        ]
      }
    },
    "/api/v1/backups/hosted/machines/{uid}": {
      "put": {
        "operationId": "setBackupsHostedMachine",
        "tags": [
          "Backups"
        ],
        "summary": "Assign a hosted machine",
        "description": "uid from GET /backups/hosted, URL-encoded (vm:abc -> vm%3Aabc). Needs a key for all clients.\n\nRequires the `backups:write` scope.",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/HostedAssignment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "assign": {
                    "type": "string",
                    "description": "A client id (number), \"ours\" or \"auto\"."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/backups/hosted/jobs/{uid}": {
      "put": {
        "operationId": "setBackupsHostedJob",
        "tags": [
          "Backups"
        ],
        "summary": "Assign a hosted job (and its machines)",
        "description": "Needs a key for all clients.\n\nRequires the `backups:write` scope.",
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/HostedAssignment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "backups:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "assign": {
                    "type": "string",
                    "description": "A client id (number), \"ours\" or \"auto\"."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}/service-levels": {
      "get": {
        "operationId": "getClientsServiceLevels",
        "tags": [
          "Service levels"
        ],
        "summary": "Get a client's SLA results",
        "description": "Requires the `service:read` scope.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Days (30, 90, 180, 365) or a named period as on the Service levels page. Default 90.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "missed_limit",
            "in": "query",
            "required": false,
            "description": "How many missed tickets to list (max 100, default 25).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ServiceLevels"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable API key (a key stops when the staff account that created it is disabled or is no longer an admin). Codes: missing_key, invalid_key, key_expired, key_revoked, key_owner_inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the permission (see the X-Required-Scope header). Codes: insufficient_scope, all_clients_required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or outside the clients the key is limited to. Codes: not_found, api_disabled, no_backup_data, no_service_data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "error.fields names each problem. Codes: validation_failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Wait for Retry-After seconds. Requests without a valid key are also limited per IP address. Codes: rate_limited, too_many_failed_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "service:read"
            ]
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from Settings → API: \"Authorization: Bearer msa_…\"."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "Meta": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          }
        }
      },
      "Key": {
        "type": "object",
        "description": "The API key making the request.",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name given to the key."
          },
          "prefix": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public part of the key."
          },
          "scopes": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Permissions, e.g. projects:write."
          },
          "client_ids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "integer",
                "null"
              ]
            },
            "description": "Clients the key is limited to; null = all clients."
          },
          "rate_limit_per_minute": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "null = never."
          },
          "api_version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "app_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "openapi": {
            "type": [
              "string",
              "null"
            ],
            "description": "Path of the OpenAPI document."
          }
        }
      },
      "Client": {
        "type": "object",
        "description": "A client (details come from the PSA and are read-only).",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "industry": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "The PSA's key (e.g. itflow) or manual."
          },
          "psa_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The client's id in the PSA, always as text (e.g. \"57\" for ITFlow). Since 2.0; before, a number for ITFlow."
          },
          "itflow_client_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Older name for psa_id, kept for existing integrations."
          },
          "main_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary_contact": {
            "type": [
              "object",
              "null"
            ],
            "description": "name, title, email, phone, mobile."
          },
          "vcio": {
            "type": [
              "object",
              "null"
            ],
            "description": "Staff user who is vCIO for the client: id, name."
          },
          "meeting_cadence": {
            "type": [
              "string",
              "null"
            ],
            "description": "How often you meet."
          },
          "in_planning": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "false = taken out of planning."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the client in the web app."
          }
        }
      },
      "ClientDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Client"
          },
          {
            "type": "object",
            "description": "A client plus a health summary.",
            "properties": {
              "summary": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "devices {total, healthy, past_end_of_life, unsupported_os, out_of_warranty}, projects {active, awaiting_decision}, next_meeting, backups {health, jobs, failed_jobs, ...}, compliance {frameworks, average_score}, alignment {score, band, reviewed_at, gaps} (2.3.0), service_levels_90d. Only the sections the key can read."
              }
            }
          }
        ]
      },
      "Contact": {
        "type": "object",
        "description": "A client contact.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "department": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "extension": {
            "type": [
              "string",
              "null"
            ]
          },
          "mobile": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "roles": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "primary, billing, technical, important, decision_maker, meeting_invitee."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Align notes."
          },
          "source": {
            "type": [
              "string",
              "null"
            ]
          },
          "psa_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact's id in the PSA (text, as psa_id on clients)."
          },
          "itflow_contact_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Older name for psa_id, kept for existing integrations."
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Device": {
        "type": "object",
        "description": "A device with its computed lifecycle.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. Server, Hypervisor host, Laptop."
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "Lifecycle category: desktop, laptop, server, network, printer, storage, power, display (2.25.0), other."
          },
          "is_virtual": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "ninja (NinjaOne), another RMM's key, the PSA's key (e.g. itflow) or manual."
          },
          "rmm": {
            "type": [
              "object",
              "null"
            ],
            "description": "For RMM devices: provider (e.g. ninjaone), device_id, organization_id. null otherwise."
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "serial": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset_tag": {
            "type": [
              "string",
              "null"
            ],
            "description": "The client's own asset tag (2.21.0), or null."
          },
          "ip_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_user": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_seen": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "os": {
            "type": [
              "object",
              "null"
            ],
            "description": "name, build, support_ends."
          },
          "lifecycle": {
            "type": [
              "object",
              "null"
            ],
            "description": "status, status_label, health (ok/warn/bad/muted), flags, in_service_date, in_service_source, in_service_estimated, age_years, lifespan_years, end_of_life, replace_by, planned_replacement {quarter, label, note, deferred}, replacement_cost, excluded, project {id, title, status, target_quarter, psa_ticket_id}: the project replacing it (2.1; its devices leave the automatic plan, replace_by null)."
          },
          "warranty": {
            "type": [
              "object",
              "null"
            ],
            "description": "end, source."
          },
          "overrides": {
            "type": [
              "object",
              "null"
            ],
            "description": "Values set in Align (null = using the synced value or policy default)."
          },
          "psa_asset_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The linked PSA asset (text, as psa_id on clients)."
          },
          "itflow_asset_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Older name for psa_asset_id, kept for existing integrations."
          },
          "ninja_device_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "NinjaOne devices only: same as rmm.device_id. Older field, kept for existing integrations."
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "psa_sync": {
            "type": [
              "object",
              "null"
            ],
            "description": "After PATCH only: status (ok, off, queued, error) and message."
          },
          "itflow_sync": {
            "type": [
              "object",
              "null"
            ],
            "description": "Older name for psa_sync, kept for existing integrations."
          }
        }
      },
      "Project": {
        "type": "object",
        "description": "A roadmap project.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "category_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "target_quarter": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "First day of the planned quarter; null = backlog."
          },
          "quarter_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. Q1 2027 or FY2027 Q3."
          },
          "cost": {
            "type": [
              "number",
              "null"
            ],
            "description": "One-time."
          },
          "recurring_monthly": {
            "type": [
              "number",
              "null"
            ]
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "description": "critical, high, medium, low."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "proposed, approved, scheduled, done, declined."
          },
          "device_ids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "integer",
                "null"
              ]
            },
            "description": "Devices this project replaces (made from Devices → Make projects); they leave the automatic replacement plan while the project isn't declined."
          },
          "psa_ticket_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The project's QUOTE- ticket in the PSA, once someone pressed Ready to start (or asked for it when the project was made); null until then."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When Ready to start was first pressed: the ticket was made, or (with no ticket possible, e.g. no PSA) the project was marked started; a ticket made later keeps this date. Null until then; projects whose ticket was made before 2.2.2 have none."
          },
          "decision": {
            "type": [
              "object",
              "null"
            ],
            "description": "Who approved or declined it (e.g. in the client portal): by, at, comment, via_portal."
          },
          "alignment_standard_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The alignment standard this project fixes (made from a gap, 2.3.0); null otherwise."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "BudgetSummary": {
        "type": "object",
        "description": "A client's three-year technology budget.",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "recurring_monthly": {
            "type": [
              "number",
              "null"
            ],
            "description": "Current recurring monthly run rate."
          },
          "selected_year": {
            "type": [
              "object",
              "null"
            ],
            "description": "label, from, to, total, one_time, by_category."
          },
          "years": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "The three plan years, same shape."
          },
          "quarters": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "label, start, end, year_index, total, by_category."
          },
          "categories": {
            "type": [
              "object",
              "null"
            ],
            "description": "Category key => label."
          },
          "lines": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "Every budget line: key, name, category, source (manual, licensing, hardware, projects, or the PSA's key for the managed-services estimate), budget_line_id, detail, monthly, one_time, tentative, by_quarter (12 values)."
          },
          "contract_dates": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "date, kind, label, name, term, auto_renew, annual_value."
          }
        }
      },
      "BudgetLine": {
        "type": "object",
        "description": "A budget line you added.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendor": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Per billing period."
          },
          "frequency": {
            "type": [
              "string",
              "null"
            ],
            "description": "monthly, quarterly, annual, one_time."
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "contract_term_months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "contract_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "notice_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "renegotiate_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "auto_renew": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "License": {
        "type": "object",
        "description": "A software license or subscription.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "The PSA's key (e.g. itflow), m365 (from the client's Microsoft 365 tenant, 2.6.0), gws (from its Google Workspace, 2.6.3) or manual."
          },
          "psa_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The license's id in the PSA (text, as psa_id on clients)."
          },
          "itflow_software_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Older name for psa_id, kept for existing integrations."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          },
          "software_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "license_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendor": {
            "type": [
              "string",
              "null"
            ]
          },
          "seats": {
            "type": [
              "integer",
              "null"
            ]
          },
          "seats_used": {
            "type": [
              "integer",
              "null"
            ]
          },
          "pricing": {
            "type": [
              "string",
              "null"
            ],
            "description": "per_seat or flat."
          },
          "unit_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "price_source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Microsoft 365 licenses (2.6.0) and Google Workspace licenses (2.6.3): list (follows the price list) or custom (this license has its own price); null for others."
          },
          "billing_cycle": {
            "type": [
              "string",
              "null"
            ]
          },
          "priced": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "cost_per_cycle": {
            "type": [
              "number",
              "null"
            ]
          },
          "monthly": {
            "type": [
              "number",
              "null"
            ]
          },
          "annual": {
            "type": [
              "number",
              "null"
            ]
          },
          "purchase_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "expire_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "auto_renew": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "contract_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "contract_term_months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "contract_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "notice_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "renegotiate_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Align notes."
          },
          "psa_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Notes from the PSA."
          },
          "itflow_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Older name for psa_notes, kept for existing integrations."
          },
          "retired": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Vendor": {
        "type": "object",
        "description": "A client's vendor (2.10.0), as its Vendors page shows it.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "The PSA's key (e.g. itflow) for vendors synced from it, else manual."
          },
          "psa_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The vendor's id in the PSA."
          },
          "template_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The vendor template it uses, or null."
          },
          "template_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "internet, phone, registrar, hosting, software, lob, security, print, hardware or other."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the PSA."
          },
          "account_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "support_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "support_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "hours": {
            "type": [
              "string",
              "null"
            ]
          },
          "sla": {
            "type": [
              "string",
              "null"
            ]
          },
          "services": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the client has from this vendor."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Align notes."
          },
          "psa_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Notes from the PSA (synced vendors)."
          },
          "from_template": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "The fields shown from the template (blank on the vendor itself)."
          },
          "retired": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "retired_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "psa (archived or deleted in the PSA) or align."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "VendorTemplate": {
        "type": "object",
        "description": "A vendor template (2.10.0): details shared by every client that uses it.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "support_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "support_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "hours": {
            "type": [
              "string",
              "null"
            ]
          },
          "sla": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "psa_template_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The PSA's vendor template it stands for, or null."
          },
          "clients": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Clients with an active vendor made from it."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Meeting": {
        "type": "object",
        "description": "A meeting.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null = internal."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ]
          },
          "type_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "scheduled, completed, cancelled."
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "attendees": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "email, name."
          },
          "agenda": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "object",
              "null"
            ],
            "description": "id, name."
          },
          "series_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "invitations_sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "invitations": {
            "type": [
              "string",
              "null"
            ],
            "description": "After POST/PATCH with send_invites: what happened to the invitations."
          }
        }
      },
      "Standard": {
        "type": "object",
        "description": "A standard in the alignment library (2.3.0).",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "section": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional heading above the category."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "why": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it matters, in the client's words."
          },
          "how": {
            "type": [
              "string",
              "null"
            ],
            "description": "How to check it (staff notes)."
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "description": "critical, high, medium, low."
          },
          "weight": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How much it counts in the score: 4, 3, 2 or 1."
          },
          "auto_check": {
            "type": [
              "string",
              "null"
            ],
            "description": "os_supported, hw_lifecycle, warranty, stale, backups, or (2.6.1) a Microsoft 365 check: m365_secure_score, m365_mfa_users, m365_mfa_admins, m365_mfa_enforced, m365_legacy_blocked, m365_admin_count, m365_stale; or (2.6.3) a Google Workspace check: gws_mfa_users, gws_mfa_admins, gws_mfa_enforced, gws_admin_count, gws_stale, gws_external_sharing, gws_third_party_apps, gws_less_secure_apps; or an email authentication check: email_spf, email_dkim, email_dmarc; or (2.7.0) huntress_agents, huntress_incidents, huntress_av, huntress_identities, huntress_ports, sat_training, sat_phishing; null = answered by hand."
          },
          "tags": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Compliance tags: answers are shared with compliance controls that have the same tags."
          },
          "suggested_fix": {
            "type": [
              "object",
              "null"
            ],
            "description": "title, category, cost: what Make project fills in."
          },
          "active": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "false = switched off (kept for old reviews)."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Alignment": {
        "type": "object",
        "description": "A client's alignment with the standards (2.3.0).",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Percent from the latest finished review; null = never reviewed (or nothing that applies)."
          },
          "band": {
            "type": [
              "string",
              "null"
            ],
            "description": "On track (80+), Needs attention (60-79), At risk (under 60), Not reviewed or No score."
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "review_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "counts": {
            "type": [
              "object",
              "null"
            ],
            "description": "aligned, misaligned, not_applicable, unanswered."
          },
          "change": {
            "type": [
              "object",
              "null"
            ],
            "description": "points since the review before, since (its date); null with fewer than two reviews."
          },
          "gaps": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "standard_id, title, priority, category, note, why, helps_with (compliance controls), project {id, title, status, target_quarter} or null. Most important first."
          },
          "not_applicable": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "standard_id, title, note."
          },
          "draft": {
            "type": [
              "object",
              "null"
            ],
            "description": "id, started_at of the open draft; null when none."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AlignmentReview": {
        "type": "object",
        "description": "An alignment review (2.3.0).",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "draft or done."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "started_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Stored when finished."
          },
          "band": {
            "type": [
              "string",
              "null"
            ]
          },
          "counts": {
            "type": [
              "object",
              "null"
            ],
            "description": "aligned, misaligned, not_applicable, unanswered."
          },
          "score_so_far": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Drafts, with answers: the score if it were finished now."
          },
          "answers": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "Single review only: standard_id, category, title, priority, auto_check, answer (aligned, misaligned, na or null), note, updated_at. A draft lists every active standard."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Changes": {
        "type": "object",
        "description": "What changed for a client since a business review (2.4.0).",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "since": {
            "type": [
              "object",
              "null"
            ],
            "description": "key, meeting_id, date, at, label, days, snapshot (then-figures saved at that review)."
          },
          "baselines": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "Completed reviews to compare with, newest first."
          },
          "headline": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "tone, title, text: a short summary."
          },
          "devices": {
            "type": [
              "object",
              "null"
            ],
            "description": "now, then, counts and lists: added, removed, replaced, warranty_expired, became_due, os_ended (devices:read)."
          },
          "projects": {
            "type": [
              "object",
              "null"
            ],
            "description": "counts and lists: done, added, approved, started, declined (projects:read)."
          },
          "spend": {
            "type": [
              "object",
              "null"
            ],
            "description": "done_cost, done_monthly, slipped (projects:read)."
          },
          "alignment": {
            "type": [
              "object",
              "null"
            ],
            "description": "then, now, change, closed and opened gaps (alignment:read)."
          },
          "compliance": {
            "type": [
              "object",
              "null"
            ],
            "description": "frameworks with now, then, change, updated (compliance:read)."
          },
          "licenses": {
            "type": [
              "object",
              "null"
            ],
            "description": "now, then, added, retired (licenses:read)."
          },
          "backup": {
            "type": [
              "object",
              "null"
            ],
            "description": "now, then (backups:read)."
          },
          "tickets": {
            "type": [
              "object",
              "null"
            ],
            "description": "opened, closed, still_open, categories (service:read)."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Health": {
        "type": "object",
        "description": "A client's health score (2.5.0).",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "0-100; null when no area has data."
          },
          "band": {
            "type": [
              "string",
              "null"
            ],
            "description": "Healthy, Needs attention, At risk or No score."
          },
          "areas_counted": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Areas with data and a weight above 0."
          },
          "areas_weighted": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Areas with a weight above 0."
          },
          "bands": {
            "type": [
              "object",
              "null"
            ],
            "description": "healthy_from, needs_attention_from (the settings in use)."
          },
          "areas": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "key (lifecycle, backups, compliance, service, alignment, security (2.6.1)), label, score (null = no data), weight, counted, details [{text, tone}] (null when the key can't read that area)."
          },
          "weakest": {
            "type": [
              "object",
              "null"
            ],
            "description": "key, score of the lowest counted area; null when none."
          },
          "since_last_review": {
            "type": [
              "object",
              "null"
            ],
            "description": "date, score then, change, label (with meetings:read) of the newest completed business review; null when there is none or no score from then."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "HealthDay": {
        "type": "object",
        "description": "A client's health on one day (2.5.0).",
        "properties": {
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null when no area had data."
          },
          "band": {
            "type": [
              "string",
              "null"
            ]
          },
          "areas": {
            "type": [
              "object",
              "null"
            ],
            "description": "lifecycle, backups, compliance, service, alignment, security (2.6.1): each 0-100 or null."
          }
        }
      },
      "AlignmentAnswersResult": {
        "type": "object",
        "description": "Result of answering standards.",
        "properties": {
          "updated": {
            "type": [
              "integer",
              "null"
            ]
          },
          "unchanged": {
            "type": [
              "integer",
              "null"
            ]
          },
          "review": {
            "type": [
              "object",
              "null"
            ],
            "description": "The review afterwards (without answers)."
          }
        }
      },
      "Framework": {
        "type": "object",
        "description": "A compliance framework.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "built_in": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "controls": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Assessment": {
        "type": "object",
        "description": "A framework assigned to a client, with its score.",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "framework_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "framework": {
            "type": [
              "string",
              "null"
            ]
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_reviewed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "next_review": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "score": {
            "type": [
              "object",
              "null"
            ],
            "description": "percent (partial counts half), met, partial, not_met, not_assessed, applicable, assessed_percent."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Control": {
        "type": "object",
        "description": "A control and the client's status for it.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "section": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "guidance": {
            "type": [
              "string",
              "null"
            ],
            "description": "What \"met\" looks like and the evidence to collect."
          },
          "automatic_check": {
            "type": [
              "string",
              "null"
            ],
            "description": "Check Align runs from its own data, if any."
          },
          "crosswalk_tags": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "evidence": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "string",
              "null"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "document_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "BulkResult": {
        "type": "object",
        "description": "Result of a bulk update.",
        "properties": {
          "updated": {
            "type": [
              "integer",
              "null"
            ]
          },
          "unchanged": {
            "type": [
              "integer",
              "null"
            ]
          },
          "assessment": {
            "type": [
              "object",
              "null"
            ],
            "description": "The framework's score afterwards."
          }
        }
      },
      "BackupSummary": {
        "type": "object",
        "description": "Backup health for one client.",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "health": {
            "type": [
              "string",
              "null"
            ],
            "description": "ok, warn, bad or muted."
          },
          "jobs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "failed_jobs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "jobs_with_warnings": {
            "type": [
              "integer",
              "null"
            ]
          },
          "protected_machines": {
            "type": [
              "integer",
              "null"
            ]
          },
          "overdue": {
            "type": [
              "integer",
              "null"
            ]
          },
          "success_rate_30d": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ClientBackups": {
        "type": "object",
        "description": "A client's backups.",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "health": {
            "type": [
              "string",
              "null"
            ]
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "stale_after_hours": {
            "type": [
              "integer",
              "null"
            ]
          },
          "stats": {
            "type": [
              "object",
              "null"
            ]
          },
          "history_30d": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "date, result (success, warning, failed, none)."
          },
          "jobs": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "uid, name, kind, status, label, health, enabled, last_run, duration_seconds, message, target, size_bytes, hosted, shared_with_other_clients."
          },
          "machines": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "uid, name, kind, status, health, newest_restore_point, restore_points, backup_bytes, device_id, hosted, not_required."
          },
          "servers_without_backup": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "device_id, name, type."
          },
          "microsoft_365": {
            "type": [
              "object",
              "null"
            ],
            "description": "health, protected_objects, users, overdue, newest_restore_point, by_type, and objects: one per user, group, team or site (uid, name, type, health, newest_restore_point, restore_points, days_with_backup, counting_since, repositories, not_required, no_longer_backed_up: no new backup for longer than the \"no longer backed up\" days, old backups kept, health \"retired\", not counted). A mailbox kept in more than one repository is one object; days_with_backup counts each day with a restore point once, from counting_since."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Exemption": {
        "type": "object",
        "description": "An item marked \"backup not required\".",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "device, workload or m365."
          },
          "device_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "item_uid": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "HostedMachine": {
        "type": "object",
        "description": "A machine on your own backup server.",
        "properties": {
          "uid": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "vm or computer."
          },
          "newest_restore_point": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "matched_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "device_name, job, manual or company."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "sorted, ours or unmatched."
          },
          "assignment": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"auto\", \"ours\" or a client id set by hand."
          },
          "device_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "job_uids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "string",
                "null"
              ]
            }
          }
        }
      },
      "HostedAssignment": {
        "type": "object",
        "description": "Result of assigning a hosted machine or job.",
        "properties": {
          "uid": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Machines."
          },
          "client_ids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "integer",
                "null"
              ]
            },
            "description": "Jobs."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "Machines."
          },
          "machines": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Jobs."
          }
        }
      },
      "ServiceLevels": {
        "type": "object",
        "description": "SLA results for a period.",
        "properties": {
          "client_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "period": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "goal_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "has_sla": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last ticket sync."
          },
          "stats": {
            "type": [
              "object",
              "null"
            ],
            "description": "tickets, responded_on_time_pct, resolved_on_time_pct, response_met/missed, resolution_met/missed, avg_first_response_minutes."
          },
          "previous_period": {
            "type": [
              "object",
              "null"
            ],
            "description": "Same shape."
          },
          "open": {
            "type": [
              "object",
              "null"
            ],
            "description": "total, past_target, close_to_target."
          },
          "monthly": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "Last 12 months, oldest first: month (YYYY-MM) plus the stats fields."
          },
          "by_priority": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            }
          },
          "missed_tickets": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "object",
                "null"
              ]
            },
            "description": "number, opened, priority, subject, missed_response, missed_resolution."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  }
}