{
  "openapi": "3.1.0",
  "info": {
    "title": "ActBook API",
    "version": "1.0.0",
    "description": "REST API for ActBook \u2014 the booking + CRM platform for live entertainers. Authenticate with a per-tenant API key (see docs/api-keys.md), sent as `Authorization: Bearer <key>` or `X-API-Key: <key>`. Responses use a JSON envelope: `{\"data\": ...}` on success, `{\"error\":{\"code\",\"message\"}}` on failure. Key scopes govern access: read (GET), write (mutations), money (payments), comms (sending contracts/messages).\n"
  },
  "servers": [
    {
      "url": "https://actbook.app/api/v1"
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "BearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "BookingSummary": {
        "type": "object",
        "properties": {
          "bookings_id": {
            "type": "integer"
          },
          "project": {
            "type": "string"
          },
          "bookings_date": {
            "type": "string",
            "format": "date"
          },
          "is_quote": {
            "type": "boolean"
          },
          "total_price": {
            "type": "number"
          },
          "project_price": {
            "type": "number"
          },
          "deposit": {
            "type": "number",
            "description": "Deposit as a percent of total (e.g. 20 = 20%). See deposit_percent/deposit_amount."
          },
          "deposit_percent": {
            "type": "number"
          },
          "deposit_amount": {
            "type": "number"
          },
          "clients_id": {
            "type": "integer"
          },
          "clients_name": {
            "type": "string"
          },
          "first_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "BookingCreate": {
        "type": "object",
        "required": [
          "date"
        ],
        "properties": {
          "client_id": {
            "type": "integer",
            "description": "Existing client; or provide `client`."
          },
          "client": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "description": "Attached as a contact; needed for a signing link"
              },
              "phone": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "zip": {
                "type": "string"
              }
            }
          },
          "email": {
            "type": "string",
            "description": "Contact email (alternative to client.email)"
          },
          "phone": {
            "type": "string"
          },
          "contact_name": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "Performance date (YYYY-MM-DD)."
          },
          "project": {
            "type": "string"
          },
          "act_id": {
            "type": "integer"
          },
          "project_type_id": {
            "type": "integer"
          },
          "price": {
            "type": "number",
            "description": "Sets project_price and total_price both. Alias: project_price."
          },
          "start_time": {
            "type": "string"
          },
          "end_time": {
            "type": "string"
          },
          "is_quote": {
            "type": "boolean",
            "default": true
          },
          "contract_template": {
            "type": "integer",
            "description": "Contract template id to use when sending a signing link (see GET /project-types / your templates). Needed for a non-blank contract."
          },
          "deposit": {
            "type": "integer",
            "description": "Deposit as a percent of total (e.g. 20 = 20%)."
          },
          "participants": {
            "type": "integer",
            "description": "Headcount for the date. Omit to inherit the project type's default_participants."
          },
          "volunteers": {
            "type": "integer",
            "description": "Volunteer count for the date. Omit to inherit the project type's default_volunteers."
          },
          "comments": {
            "type": "string",
            "description": "Client-facing notes that PRINT on the contract."
          },
          "manager_notes": {
            "type": "string",
            "description": "Internal notes that do NOT print on the contract (e.g. provenance like \"from handwritten note\")."
          },
          "project_price": {
            "type": "number",
            "description": "Base price (alias of price)."
          },
          "total_price": {
            "type": "number",
            "description": "Grand total (base + line items); defaults to the base price."
          }
        }
      },
      "Client": {
        "type": "object",
        "properties": {
          "clients_id": {
            "type": "integer"
          },
          "clients_name": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "zip": {
            "type": "string"
          },
          "booking_count": {
            "type": "integer"
          },
          "contacts": {
            "type": "array",
            "description": "Contacts belonging to this client (returned by GET /clients/{id}). Each contact's clients_contacts_id can be used as a booking's primary contact; email/phone carry the address a contract signing-link is sent to.",
            "items": {
              "type": "object",
              "properties": {
                "clients_contacts_id": { "type": "integer" },
                "contacts_first_name": { "type": "string" },
                "contacts_last_name": { "type": "string" },
                "email": { "type": "string", "nullable": true },
                "phone": { "type": "string", "nullable": true }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/me": {
      "get": {
        "summary": "Current key context (tenant, user, scopes)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/waivers": {
      "get": {
        "summary": "List signed waivers",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["signed", "void", "spam"] } },
          { "name": "q", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/waivers/{id}": {
      "get": {
        "summary": "Get one signed waiver (with its pass URL)",
        "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ],
        "responses": { "200": { "description": "OK" }, "404": { "description": "Not found" } }
      },
      "patch": {
        "summary": "Void or restore a waiver",
        "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ],
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "enum": ["signed", "void"] } } } } } },
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/waiver-templates": {
      "get": {
        "summary": "List waiver templates (adult + minor wording)",
        "responses": { "200": { "description": "OK" } }
      },
      "post": {
        "summary": "Create or update a waiver template",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "kind": { "type": "string", "enum": ["individual", "minor"] }, "title": { "type": "string" }, "body_html": { "type": "string" } }, "required": ["body_html"] } } } },
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/inquiries": {
      "get": {
        "summary": "List contact-form inquiries (leads)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "read",
                "converted",
                "archived"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/inquiries/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "Get one inquiry",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update inquiry status (new|read|converted|archived|spam)",
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      }
    },
    "/bookings": {
      "get": {
        "summary": "List bookings",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search project or client name"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "confirmed",
                "quotes"
              ]
            }
          },
          {
            "name": "upcoming",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bookings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "bookings": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BookingSummary"
                          }
                        },
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a booking (optionally creating the client inline)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/bookings/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "Get a booking with its dates and line items",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update booking fields (project, comments, manager_notes, price, is_quote, contract_template, deposit)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project": {
                    "type": "string"
                  },
                  "comments": {
                    "type": "string",
                    "description": "Client-facing notes that PRINT on the contract."
                  },
                  "manager_notes": {
                    "type": "string",
                    "description": "Internal notes that do NOT print on the contract."
                  },
                  "price": {
                    "type": "number"
                  },
                  "is_quote": {
                    "type": "boolean"
                  },
                  "contract_template": {
                    "type": "integer",
                    "description": "Contract template id (needed for a non-blank signing-link contract)."
                  },
                  "deposit": {
                    "type": "integer",
                    "description": "Deposit percent (e.g. 20)."
                  },
                  "participants": {
                    "type": "integer",
                    "description": "Headcount, applied to the booking's date(s)."
                  },
                  "volunteers": {
                    "type": "integer",
                    "description": "Volunteer count, applied to the booking's date(s)."
                  },
                  "project_price": {
                    "type": "number",
                    "description": "Base price."
                  },
                  "total_price": {
                    "type": "number",
                    "description": "Grand total."
                  },
                  "client_id": {
                    "type": "integer",
                    "description": "Reassign the booking to a different existing client."
                  },
                  "project_type_id": {
                    "type": "integer",
                    "description": "Event type \u2014 set on all the booking's dates (projects_dates.type). Null clears it."
                  },
                  "contact_id": {
                    "type": "integer",
                    "description": "Set the booking's contact (whose name/email/phone print on the contract and get the signing link) to an existing contact on the client \u2014 no duplicate created. Must already be attached to the client (POST /clients/{id}/contacts first if not). Null clears it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      },
      "delete": {
        "summary": "Soft-delete a booking",
        "responses": {
          "200": {
            "description": "Deleted"
          }
        }
      }
    },
    "/bookings/{id}/signing-link": {
      "post": {
        "summary": "Email or SMS the client a contract signing link (requires comms scope)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "method": {
                    "type": "string",
                    "enum": [
                      "email",
                      "sms"
                    ],
                    "default": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent"
          },
          "403": {
            "description": "Missing comms scope"
          },
          "502": {
            "description": "Delivery failed (transient)"
          },
          "503": {
            "description": "Channel not configured for this tenant (e.g. SMS not provisioned). Check GET /me channels first."
          }
        }
      }
    },
    "/clients": {
      "get": {
        "summary": "List clients",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clients"
          }
        }
      },
      "post": {
        "summary": "Create a client",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "description": "Stored as a contact (that's where signing links look)"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "contact_name": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created (returns client_id and contact_id)"
          }
        }
      }
    },
    "/clients/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "Get a client",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update a client",
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      },
      "delete": {
        "summary": "Archive (soft-delete) a client",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Archived"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/clients/{id}/merge": {
      "post": {
        "summary": "Merge this (duplicate) client into another; source is soft-deleted",
        "description": "Moves the source client's bookings, contacts, and comments onto the survivor, then soft-deletes the source.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "into"
                ],
                "properties": {
                  "into": {
                    "type": "integer",
                    "description": "The surviving client_id to merge INTO."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merged"
          },
          "422": {
            "description": "Validation error"
          }
        }
      }
    },
    "/clients/{id}/contacts": {
      "post": {
        "summary": "Add a contact (name/email/phone) to a client",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/clients/{id}/contacts/{contactId}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        },
        {
          "name": "contactId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Edit a contact on a client (name, title, email, phone)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "404": {
            "description": "Contact not found for this client"
          }
        }
      },
      "delete": {
        "summary": "Remove a contact from a client (soft-delete)",
        "description": "Detaches the contact from this client. If it then belongs to no client, the contact record is soft-deleted (clients_contacts.active='0') \u2014 kept for history/recovery but hidden from search and reads, and not assignable. Response includes soft_deleted (bool).",
        "responses": {
          "200": {
            "description": "Removed"
          },
          "404": {
            "description": "Contact not found for this client"
          }
        }
      }
    },
    "/clients/{id}/bookings": {
      "get": {
        "summary": "List a client's bookings",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts": {
      "get": {
        "summary": "List acts",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create an act",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "day_rate": {
                    "type": "number"
                  },
                  "travel_fee": {
                    "type": "number"
                  },
                  "travel_radius_miles": {
                    "type": "integer"
                  },
                  "notes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/project-types": {
      "get": {
        "summary": "List event types / packages",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create an event type / package",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "cost": {
                    "type": "number",
                    "description": "Base price. Flat = the price; per_guest = base covering included_participants; block = price of the first circuit."
                  },
                  "price_mode": {
                    "type": "string",
                    "enum": [
                      "flat",
                      "per_guest",
                      "block",
                      "menu"
                    ],
                    "description": "flat = cost. per_guest = cost covers included_participants, then +price_per_participant each guest. block = cost buys a circuit of block_size (+block_tolerance stretch), +block_extra_price per extra circuit."
                  },
                  "included_participants": {
                    "type": "integer",
                    "description": "per_guest \u2014 guests covered by the base cost."
                  },
                  "price_per_participant": {
                    "type": "number",
                    "description": "per_guest \u2014 rate for each guest beyond included_participants."
                  },
                  "block_size": {
                    "type": "integer",
                    "description": "block \u2014 guests per circuit."
                  },
                  "block_tolerance": {
                    "type": "integer",
                    "description": "block \u2014 extra guests allowed per circuit before another is forced."
                  },
                  "block_extra_price": {
                    "type": "number",
                    "description": "block \u2014 price per additional circuit."
                  },
                  "min_participants": {
                    "type": "integer",
                    "description": "Minimum guest count required to book (per_guest/block)."
                  },
                  "setup_length": {
                    "type": "integer"
                  },
                  "running_length": {
                    "type": "integer"
                  },
                  "teardown_length": {
                    "type": "integer"
                  },
                  "bookable_online": {
                    "type": "boolean"
                  },
                  "at_facility": {
                    "type": "boolean",
                    "description": "Event is held at your facility; the booking form hides the venue and shows your address."
                  },
                  "menu_min_select": {
                    "type": "integer",
                    "description": "menu mode \u2014 minimum options the client must select."
                  },
                  "menu_per_guests": {
                    "type": "integer",
                    "description": "menu mode \u2014 require at least one option per N guests."
                  },
                  "menu_minutes_per_extra": {
                    "type": "integer",
                    "description": "menu mode \u2014 extra running minutes per option beyond menu_min_select."
                  },
                  "lodging_over_miles": {
                    "type": "integer",
                    "description": "Require lodging when the venue is farther than this many miles (one-way driving)."
                  },
                  "act_id": {
                    "type": "integer"
                  },
                  "default_participants": {
                    "type": "integer"
                  },
                  "default_volunteers": {
                    "type": "integer"
                  },
                  "max_per_day": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/project-types/{id}": {
      "patch": {
        "summary": "Update an event type / package",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete an event type / package",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/bookings/{id}/invoice": {
      "get": {
        "summary": "Invoice view \u2014 line items, payments, totals and balance",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice totals. 'paid' counts only real client receipts. 'balance' = grand_total - paid and may be negative on overpayment; when it is, 'overpaid' is true, 'credit' holds the positive overpaid amount, and 'warnings' contains an {code:\"overpaid\"} entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "invoice": {
                          "type": "object",
                          "properties": {
                            "booking_id": { "type": "integer" },
                            "project": { "type": "string" },
                            "project_price": { "type": "number" },
                            "line_items": { "type": "array", "items": { "type": "object" } },
                            "line_total": { "type": "number" },
                            "grand_total": { "type": "number" },
                            "paid": { "type": "number", "description": "Sum of real client receipts only." },
                            "balance": { "type": "number", "description": "grand_total - paid; negative when overpaid." },
                            "overpaid": { "type": "boolean", "description": "True when recorded payments exceed grand_total." },
                            "credit": { "type": "number", "description": "Positive overpaid amount (0 when not overpaid); needs a refund or credit." },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "code": { "type": "string" },
                                  "message": { "type": "string" }
                                }
                              }
                            },
                            "payments": { "type": "array", "items": { "type": "object" } }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/bookings/{id}/payments": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "List payments recorded against a booking",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Record a payment (requires money scope)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "number"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Recorded"
          },
          "403": {
            "description": "Missing money scope"
          }
        }
      }
    },
    "/bookings/{id}/line-items": {
      "get": {
        "summary": "List line items on a booking",
        "description": "Extra charges / add-ons. The booking total is project_price + sum(line amounts).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Add a line item to a booking",
        "description": "Adds an extra charge (amount = price * quantity) and increases the booking total accordingly.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "description",
                  "price"
                ],
                "properties": {
                  "description": {
                    "type": "string"
                  },
                  "price": {
                    "type": "number",
                    "description": "Unit price in dollars"
                  },
                  "quantity": {
                    "type": "number",
                    "default": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "404": {
            "description": "Not found"
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/bookings/{id}/line-items/{lineId}": {
      "delete": {
        "summary": "Remove a line item from a booking",
        "description": "Soft-deletes the line and decreases the booking total by its amount.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/bookings/{id}/dates": {
      "get": {
        "summary": "List a booking's event dates (with event type, times, location, headcount)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "Add an event date to a booking",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "date"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "type": {
                    "type": "integer",
                    "description": "project_type_id for this date (the event type). Null clears it."
                  },
                  "start_time": {
                    "type": "string"
                  },
                  "end_time": {
                    "type": "string"
                  },
                  "setup_time": {
                    "type": "string"
                  },
                  "done_time": {
                    "type": "string"
                  },
                  "segment_price": {
                    "type": "number"
                  },
                  "participants": {
                    "type": "integer"
                  },
                  "volunteers": {
                    "type": "integer"
                  },
                  "venue": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "location": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "is_public": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "422": {
            "description": "Validation error"
          }
        }
      }
    },
    "/bookings/{id}/dates/{dateId}": {
      "patch": {
        "summary": "Update an event date \u2014 event type, times, location, headcount, etc.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "dateId",
            "in": "path",
            "required": true,
            "description": "The project_date_id of the gig date (as returned in project_date_id / projects_dates_id on GET, and by POST/PATCH/DELETE).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "type": {
                    "type": "integer",
                    "description": "project_type_id for this date (the event type). Null clears it."
                  },
                  "start_time": {
                    "type": "string"
                  },
                  "end_time": {
                    "type": "string"
                  },
                  "setup_time": {
                    "type": "string"
                  },
                  "done_time": {
                    "type": "string"
                  },
                  "segment_price": {
                    "type": "number"
                  },
                  "participants": {
                    "type": "integer"
                  },
                  "volunteers": {
                    "type": "integer"
                  },
                  "venue": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "location": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string"
                  },
                  "is_public": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Remove an event date from a booking",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "dateId",
            "in": "path",
            "required": true,
            "description": "The project_date_id of the gig date (as returned in project_date_id / projects_dates_id on GET, and by POST/PATCH/DELETE).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "summary": "List webhook subscriptions",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a webhook subscription (returns the HMAC signing secret)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "target_url"
                ],
                "properties": {
                  "target_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ],
                    "description": "'all', a CSV string, or an array of event names."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "summary": "Delete a webhook subscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          }
        }
      }
    },
    "/availability/blackouts": {
      "get": {
        "summary": "List blackout date ranges",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Add a blackout range",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "start_date"
                ],
                "properties": {
                  "start_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "end_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/availability/blackouts/{id}": {
      "delete": {
        "summary": "Remove a blackout range",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          }
        }
      }
    },
    "/availability": {
      "get": {
        "summary": "Booked dates, or free/busy for a specific date",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "If given, returns free/booked_count for that date."
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/contract-templates": {
      "get": {
        "summary": "List contract templates (id, title, kind: client|act, is_default)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/contract-templates/{id}": {
      "patch": {
        "summary": "Make this template the default for its kind",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "default"
                ],
                "properties": {
                  "default": {
                    "type": "boolean",
                    "description": "Must be true; a default is moved, never cleared."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/settings": {
      "get": {
        "summary": "All tenant settings (company info, booking config, labels, notification prefs). Each value has an 'editable' flag; platform-managed/secret keys are read-only.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "summary": "Update one or more settings",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "example": {
                  "company_name": "Cirque Amongus",
                  "return_email": "info@cirqueamongus.com"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK \u2014 returns {updated, rejected}"
          }
        }
      }
    },
    "/acts/{id}": {
      "patch": {
        "summary": "Update an act",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Delete an act",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/messages": {
      "get": {
        "summary": "SMS conversation inbox (one row per client, unread counts)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Send a text (comms scope). Supports dry_run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "body"
                ],
                "properties": {
                  "body": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "integer",
                    "description": "Recipient by client; uses the number they last texted from, else the number on file."
                  },
                  "to": {
                    "type": "string",
                    "description": "Explicit E.164 number, if not sending to a known client."
                  },
                  "dry_run": {
                    "type": "boolean",
                    "description": "Preview without sending."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent, or a dry-run preview"
          }
        }
      }
    },
    "/messages/{clientId}": {
      "get": {
        "summary": "One conversation thread, oldest first",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/automations": {
      "get": {
        "summary": "The day-of SMS, thank-you and re-book automations",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/automations/{type}": {
      "patch": {
        "summary": "Update one automation (day_of_sms | thankyou_email | rebook_email)",
        "parameters": [
          {
            "name": "type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "day_of_sms",
                "thankyou_email",
                "rebook_email"
              ]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "offset_days": {
                    "type": "integer"
                  },
                  "send_hour": {
                    "type": "integer"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "channel": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Unknown automation"
          }
        }
      }
    },
    "/outreach": {
      "get": {
        "summary": "List cold-email campaigns",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a campaign (draft)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "from_name": {
                    "type": "string"
                  },
                  "from_email": {
                    "type": "string"
                  },
                  "reply_to": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/outreach/{id}": {
      "get": {
        "summary": "One campaign, with its steps",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "patch": {
        "summary": "Set campaign status (active requires comms scope; the cron drips within the daily cap)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "active",
                      "paused"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/outreach/{id}/steps": {
      "post": {
        "summary": "Replace the email sequence",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "body": {
                          "type": "string"
                        },
                        "delay_days": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/outreach/{id}/prospects": {
      "post": {
        "summary": "Enroll prospects (dry_run to preview)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "prospects": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": "string"
                        },
                        "first_name": {
                          "type": "string"
                        },
                        "last_name": {
                          "type": "string"
                        },
                        "company": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "dry_run": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK \u2014 {enrolled: {added, duplicate, suppressed, invalid}}"
          }
        }
      }
    },
    "/schedule": {
      "get": {
        "summary": "Upcoming gigs on the calendar",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/reports": {
      "get": {
        "summary": "Business summary (upcoming gigs, bookings this month, open quotes, active clients, booked value YTD)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/users": {
      "get": {
        "summary": "List team members",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a login user",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "login": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "access_level": {
                    "type": "string"
                  },
                  "is_admin": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "login",
                  "password"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/managers": {
      "get": {
        "summary": "List performers/staff",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Add a team member",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "nick"
                ],
                "properties": {
                  "nick": {
                    "type": "string"
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/managers/{id}": {
      "patch": {
        "summary": "Update a team member",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Remove a team member",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/assignments": {
      "get": {
        "summary": "Who is assigned to a gig date",
        "parameters": [
          {
            "name": "project_date_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Assign a team member to a gig date",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "manager_id",
                  "project_date_id"
                ],
                "properties": {
                  "manager_id": {
                    "type": "integer"
                  },
                  "project_date_id": {
                    "type": "integer"
                  },
                  "start_time": {
                    "type": "string"
                  },
                  "end_time": {
                    "type": "string"
                  },
                  "hourly": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Unassign",
        "parameters": [
          {
            "name": "manager_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "project_date_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/contract-clauses": {
      "get": {
        "summary": "List contract clauses",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a contract clause",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "clause": {
                    "type": "string"
                  },
                  "conditional": {
                    "type": "string"
                  },
                  "client_clause": {
                    "type": "boolean"
                  },
                  "act_clause": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "title"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/contract-clauses/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a contract clause",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "clause": {
                    "type": "string"
                  },
                  "conditional": {
                    "type": "string"
                  },
                  "client_clause": {
                    "type": "boolean"
                  },
                  "act_clause": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a contract clause",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/letters": {
      "get": {
        "summary": "List letters",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a letter",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "default": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "title"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/letters/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a letter",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "default": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a letter",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/comments": {
      "get": {
        "summary": "List activity notes on a client or booking",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "booking_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Add an activity note",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "integer"
                  },
                  "booking_id": {
                    "type": "integer"
                  },
                  "comment": {
                    "type": "string"
                  },
                  "sticky": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "comment"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/comments/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a comment",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "comment": {
                    "type": "string"
                  },
                  "sticky": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a comment",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/project-notes": {
      "get": {
        "summary": "List shared notepad notes",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Add a project note",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string"
                  }
                },
                "required": [
                  "note"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/project-notes/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a project note",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string"
                  },
                  "handled": {
                    "type": "boolean"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a project note",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/todos": {
      "get": {
        "summary": "List to-dos",
        "parameters": [
          {
            "name": "done",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Create a to-do",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "item": {
                    "type": "string"
                  },
                  "details": {
                    "type": "string"
                  },
                  "due_date": {
                    "type": "string"
                  },
                  "urgent": {
                    "type": "boolean"
                  },
                  "is_global": {
                    "type": "boolean"
                  },
                  "booking_id": {
                    "type": "integer"
                  },
                  "client_id": {
                    "type": "integer"
                  },
                  "project_date_id": {
                    "type": "integer"
                  }
                },
                "required": [
                  "item"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/todos/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a to-do",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "item": {
                    "type": "string"
                  },
                  "details": {
                    "type": "string"
                  },
                  "done": {
                    "type": "boolean"
                  },
                  "urgent": {
                    "type": "boolean"
                  },
                  "is_global": {
                    "type": "boolean"
                  },
                  "due_date": {
                    "type": "string"
                  },
                  "booking_id": {
                    "type": "integer"
                  },
                  "client_id": {
                    "type": "integer"
                  },
                  "project_date_id": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a to-do",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/mailboxes": {
      "get": {
        "summary": "List email accounts (secrets hidden)",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/mailboxes/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "Get one email account",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/users/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "patch": {
        "summary": "Update a login user",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "is_admin": {
                    "type": "boolean"
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "access_level": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "delete": {
        "summary": "Delete a login user",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/search": {
      "get": {
        "summary": "Quick search across clients and their contacts",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/emails": {
      "post": {
        "summary": "Send an email to a client or address (logs to client history)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string"
                  },
                  "to_name": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "integer"
                  },
                  "booking_id": {
                    "type": "integer"
                  },
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "html": {
                    "type": "boolean"
                  },
                  "dry_run": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "subject",
                  "body"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/inquiries/{id}/reply": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "post": {
        "summary": "Email a reply to an inquiry (marks it read)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "subject": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "integer"
                  },
                  "html": {
                    "type": "boolean"
                  },
                  "dry_run": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/bookings/{id}/contract": {
      "get": {
        "summary": "Render a booking's contract (html or pdf via ?format=pdf)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "html",
                "pdf"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/bookings/{id}/request-payment": {
      "post": {
        "summary": "Email/text the client a link to pay a deposit or balance (money scope)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "deposit",
                      "balance"
                    ]
                  },
                  "amount": {
                    "type": "number"
                  },
                  "email": {
                    "type": "boolean"
                  },
                  "sms": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sent"
          },
          "409": {
            "description": "Deposit already paid (error.code=deposit_already_paid)."
          },
          "422": {
            "description": "Caller error — e.g. no deposit amount set, or no email/phone on file (error.code=no_amount|no_email|no_phone|no_channel)."
          },
          "502": {
            "description": "The request could not be saved or sent (error.code=save_failed|send_failed)."
          },
          "503": {
            "description": "Online payments are not set up: Stripe is not configured on the platform, or this account has not connected its Stripe account (error.code=stripe_not_configured|stripe_not_connected)."
          }
        }
      }
    },
    "/feedback": {
      "post": {
        "summary": "Submit a bug report / suggestion",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "page": {
                    "type": "string"
                  },
                  "steps": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Logged"
          }
        }
      },
      "get": {
        "summary": "List feedback for this tenant",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/feedback/{id}": {
      "patch": {
        "summary": "Update a feedback item's status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      },
      "delete": {
        "summary": "Delete a feedback item (write scope)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Feedback not found (error.code=not_found)."
          }
        }
      }
    },
    "/referrals": {
      "get": {
        "summary": "Network gig referrals \u2014 the open pool, or ?type=mine / ?type=claims",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pool",
                "mine",
                "claims"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "Not opted in"
          }
        }
      },
      "post": {
        "summary": "Refer one of your bookings to the network pool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "booking_id"
                ],
                "properties": {
                  "booking_id": {
                    "type": "integer"
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Referred"
          }
        }
      }
    },
    "/referrals/{id}": {
      "delete": {
        "summary": "Withdraw an open referral you posted",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/referrals/{id}/claim": {
      "post": {
        "summary": "Claim an open network referral (reveals the client contact)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Claimed"
          },
          "409": {
            "description": "Not claimable"
          }
        }
      }
    },
    "/gift-certificates": {
      "get": {
        "summary": "List gift certificates (?redeemed=0|1|all)",
        "parameters": [
          {
            "name": "redeemed",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Issue a gift certificate (money scope)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "number"
                  },
                  "code": {
                    "type": "string"
                  },
                  "purchaser": {
                    "type": "string"
                  },
                  "recipient": {
                    "type": "string"
                  },
                  "paid_amount": {
                    "type": "number"
                  },
                  "paid_by": {
                    "type": "string"
                  },
                  "comments": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Issued"
          }
        }
      }
    },
    "/gift-certificates/{id}": {
      "get": {
        "summary": "Get a gift certificate by cert_no",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "Update a gift certificate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      },
      "delete": {
        "summary": "Delete a gift certificate (money scope). Refuses a redeemed certificate.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Gift certificate not found (error.code=not_found)."
          },
          "409": {
            "description": "The certificate has been redeemed and cannot be deleted (error.code=already_redeemed)."
          }
        }
      }
    },
    "/project-types/{id}/options": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "summary": "List a menu package's a-la-carte options",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "summary": "Replace a menu package's options (the whole set)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "options"
                ],
                "properties": {
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "price": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved"
          }
        }
      }
    }
  }
}
