{
  "openapi": "3.0.3",
  "info": {
    "title": "MyLawyerLink API",
    "version": "1.0.0",
    "description": "API for MyLawyerLink web and mobile clients"
  },
  "servers": [],
  "paths": {
    "/sitemap.xml": {
      "get": {
        "summary": "Public XML sitemap",
        "description": "URL list for search engines (marketing pages, blog index, individual blog posts). Generated at build time when prerendered.",
        "tags": [
          "Public"
        ],
        "responses": {
          "200": {
            "description": "Sitemap XML document (urlset per sitemaps.org)",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string",
                  "format": "xml"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/sign/{id}": {
      "get": {
        "summary": "Redirect signer to DocuSeal signing URL",
        "description": "Public redirect endpoint used by email links. Validates signature id, active status, expiration, and URL safety before redirecting to the signer URL.",
        "tags": [
          "DocuSeal"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "DocumentSignature ID"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to validated signerUrl"
          },
          "400": {
            "description": "Invalid signature link or malformed signer URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Signature link not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Signature request no longer active or expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/user/timezone": {
      "put": {
        "summary": "Update user timezone",
        "description": "Set the current user's timezone (IANA value).",
        "tags": [
          "User"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "timezone"
                ],
                "properties": {
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "timezone": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid timezone",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/user/profile": {
      "put": {
        "summary": "Update profile (username, first name, last name)",
        "description": "Updates the authenticated user's display profile. At least one of username, firstName, or lastName must be sent.\nOmit a field to leave it unchanged; send null for firstName or lastName to clear.\n",
        "tags": [
          "User"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserProfileUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserProfileUpdateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Username already taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/user/phone-number": {
      "get": {
        "summary": "Get user phone number",
        "description": "Get the current user's personal phone number.",
        "tags": [
          "User"
        ],
        "responses": {
          "200": {
            "description": "User phone number",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "phoneNumber": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update user phone number",
        "description": "Update the current user's personal phone number.",
        "tags": [
          "User"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phoneNumber"
                ],
                "properties": {
                  "phoneNumber": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "phoneNumber": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/user/call-do-not-disturb": {
      "put": {
        "summary": "Update call do-not-disturb (browser ring)",
        "description": "Sets or clears per-team inbound call browser ring suppression for the current user.\nDoes not block PSTN forward or voicemail; only excludes the user from Twilio Client dial.\n",
        "tags": [
          "User"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserCallDoNotDisturbUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserCallDoNotDisturbResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/tokens": {
      "get": {
        "summary": "Get Twilio access tokens for all teams",
        "description": "Generate Twilio Access Tokens for every team the user belongs to. Use for multi-team call answering (e.g. mobile app) so the client can register one Device per token and receive incoming calls from any team number.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "voicePlatform",
            "schema": {
              "type": "string",
              "enum": [
                "android",
                "ios"
              ]
            },
            "description": "When set, each token's Voice grant includes that team's pushCredentialSid when configured (else omitted; see voicePushCredentialConfigured)."
          }
        ],
        "responses": {
          "200": {
            "description": "One token per team (teams with Twilio configured and at least one phone number).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TwilioTokensResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid voicePlatform query (must be android or ios when provided)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/token": {
      "get": {
        "summary": "Get Twilio access token",
        "description": "Generate Twilio Access Token for browser-based calling (Voice SDK). Optional query teamId to use a specific team. Returns JWT and list of team phone numbers.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "teamId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Team to use (user must be member). Omit for current session team."
          },
          {
            "in": "query",
            "name": "voicePlatform",
            "schema": {
              "type": "string",
              "enum": [
                "android",
                "ios"
              ]
            },
            "description": "When set, Voice grant includes pushCredentialSid for inbound mobile notify (FCM/APNs). Requires per-team or env push credential SID."
          }
        ],
        "responses": {
          "200": {
            "description": "Access token and phone numbers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TwilioTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "No team context, Twilio not configured, no phone numbers, invalid voicePlatform query value, or voicePlatform set without a resolvable push credential SID",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ErrorResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "VOICE_PUSH_CREDENTIAL_MISSING"
                          ],
                          "description": "Present when voicePlatform is android or ios and no push credential SID was found (team DB + env fallback)."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not a member of the requested team",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/quick-transfer-numbers": {
      "get": {
        "summary": "List quick transfer numbers",
        "description": "List the team's quick transfer numbers (for forward/transfer dial). Requires calls.update and active subscription.",
        "tags": [
          "Twilio"
        ],
        "responses": {
          "200": {
            "description": "List of quick transfer numbers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "quickTransferNumbers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/QuickTransferNumber"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create quick transfer number",
        "description": "Add a number to the team's quick transfer list. Requires calls.update and active subscription.",
        "tags": [
          "Twilio"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuickTransferNumberCreateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quick transfer number created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QuickTransferNumber"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or no team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/quick-transfer-numbers/{id}": {
      "patch": {
        "summary": "Update quick transfer number",
        "description": "Update label, phone number, or sort order. Requires calls.update and active subscription.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Quick transfer number ID"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuickTransferNumberUpdateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quick transfer number updated (or unchanged if body empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QuickTransferNumber"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Quick transfer number not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete quick transfer number",
        "description": "Remove a number from the team's quick transfer list. Requires calls.update and active subscription.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Quick transfer number ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Quick transfer number deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Quick transfer number not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/phone-numbers": {
      "get": {
        "summary": "List phone numbers",
        "description": "List all phone numbers for the current team plus numbers in the Twilio account available to assign. Cached for 5 minutes.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team override for messenger-local team tabs."
          }
        ],
        "responses": {
          "200": {
            "description": "Team phone numbers and available-to-assign list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneNumbersListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, no team context, or Twilio credentials not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (not a member of requested team)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Search, purchase, or assign phone number",
        "description": "Search for available numbers (action=search), purchase a number (action=purchase), or assign an existing Twilio number to this team (action=assign). Body uses discriminated action.",
        "tags": [
          "Twilio"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneNumberSearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results (availableNumbers), purchase success (phoneNumber), or assign success (phoneNumber)",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AvailableNumbersResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PhoneNumberPurchaseResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (insufficient permission or subscription)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Number not found (e.g. assign with invalid twilioSid)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/phone-numbers/{id}": {
      "put": {
        "summary": "Update team phone number",
        "description": "Update forwarding, voicemail, friendly name, primary flag, reassign to another firm team (`assignToTeamId`), or trigger Twilio webhook configuration.\nRequires firm (billing) team context and `phone.update`. Returns the updated `PhoneNumber` row as `phoneNumber`.\n",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Phone number record ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TwilioPhoneNumberUpdateBody"
              },
              "examples": {
                "forwardAndTimeout": {
                  "summary": "Set forward destination and ring duration",
                  "value": {
                    "forwardToNumber": "+15551234567",
                    "forwardDialTimeoutSeconds": 30
                  }
                },
                "setPrimary": {
                  "summary": "Set as primary for the team",
                  "value": {
                    "isPrimary": true
                  }
                },
                "reassignToChildTeam": {
                  "summary": "Move number to another team in the firm (keeps Twilio inventory)",
                  "value": {
                    "assignToTeamId": "00000000-0000-4000-8000-000000000003"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "phoneNumber"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "phoneNumber": {
                      "$ref": "#/components/schemas/TwilioPhoneNumber"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "phoneNumber": {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "teamId": "00000000-0000-4000-8000-000000000002",
                    "phoneNumber": "+15559876543",
                    "forwardToNumber": "+15551234567",
                    "forwardDialTimeoutSeconds": 30,
                    "isPrimary": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, primary blocked (webhooks not configured), or Twilio error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Wrong team context (not billing team) or missing `phone.update`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found for this firm",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Release or remove team phone number",
        "description": "Removes this team's row for the number, or releases the number from Twilio when this firm was the last user.\nRequires firm (billing) team context, session auth, `phone.delete`, and an active subscription.\nNo request body.\n",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Phone number record ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Number removed from team or fully released from Twilio",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "message"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "message": {
                      "type": "string",
                      "example": "Phone number released successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No team context, Twilio credentials not configured, or subscription issue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not billing team context or missing `phone.delete`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found for this firm",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/messages": {
      "get": {
        "summary": "Get conversation messages",
        "description": "Get messages for a conversation. Pass phoneNumberId and either toNumber (single number) or clientId (all messages with that client's numbers).",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "phoneNumberId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "toNumber",
            "schema": {
              "type": "string"
            },
            "description": "Required when clientId is not provided. The other party's number for a single conversation."
          },
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "When provided, returns messages to/from any of this client's phone numbers (combines conversations)."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max messages per page (latest first)"
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            },
            "description": "Number of messages to skip (for loading older)"
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team override for messenger-local team tabs."
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated messages (latest first); scroll up to load older via offset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "messages": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MessageItem"
                          }
                        }
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, missing team context, or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a member, missing permission, or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Send SMS or MMS",
        "description": "Send an SMS or MMS from a team phone number. Optional mediaUrls (HTTPS template-attachment blobs, max 10) become MMS; body may be empty when mediaUrls is non-empty.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional UUID; when Redis is configured, concurrent duplicate POSTs coordinate via SET NX EX on the same key (short lock) then store the successful response for 7 days for replay."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid body, invalid teamId, no team context, or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a member, missing permission, or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/messages/upload-handler": {
      "post": {
        "summary": "Vercel Blob client upload callback for message compose MMS attachments",
        "description": "JSON body from @vercel/blob/client handleUpload. Pathname must be teams/{teamId}/template-attachments/sms/...; teamId is authorized with calls.view (messenger team may differ from session team).",
        "tags": [
          "Twilio"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlobClientUploadCallbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload token payload or completion acknowledgement",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BlobClientUploadTokenResponse"
                    },
                    {
                      "$ref": "#/components/schemas/BlobClientUploadCompletedAckResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid upload path",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Blob storage not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/messages/unread": {
      "get": {
        "summary": "Get unread messages and threads",
        "description": "Returns unread count and paginated SMS conversation threads for the current team, plus twilioConfigured so clients can skip a separate credentials check. Use limit and offset for infinite scroll. Requires team context, calls.view permission, and an active subscription.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Number of threads to return per page"
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            },
            "description": "Number of threads to skip (for pagination)"
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team override for messenger-local team tabs."
          },
          {
            "in": "query",
            "name": "countOnly",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "1",
                "0"
              ]
            },
            "description": "When true or 1, returns unreadCount only with empty threads (lighter than full thread build on cache miss)."
          }
        ],
        "responses": {
          "200": {
            "description": "Unread count and paginated threads plus pagination metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnreadThreadsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (e.g. invalid limit/offset)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a team member, insufficient permissions, or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Mark conversation as unread",
        "description": "Marks all read inbound messages in a conversation as unread. Requires team context, calls.view permission, and active subscription.",
        "tags": [
          "Messages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnreadMessagesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Messages marked as unread",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnreadMessagesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing team context or required body fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a team member, insufficient permissions, or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found or inactive",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/messages/read": {
      "post": {
        "summary": "Mark messages as read",
        "description": "Marks all unread inbound messages in a conversation as read. Requires team context, calls.view permission, and active subscription.",
        "tags": [
          "Twilio",
          "Messages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phoneNumberId",
                  "toNumber"
                ],
                "properties": {
                  "phoneNumberId": {
                    "type": "string",
                    "description": "Team phone number ID for the conversation"
                  },
                  "toNumber": {
                    "type": "string",
                    "description": "Other party phone number (conversation identifier)"
                  },
                  "teamId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Optional team override for messenger-local team tabs."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Messages marked as read",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "markedRead": {
                      "type": "integer",
                      "description": "Number of messages marked read"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body, invalid teamId, missing team context, or required fields missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a team member, insufficient permissions, or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found or inactive",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/messages/{messageId}/media/{index}": {
      "get": {
        "operationId": "getTwilioMessageMedia",
        "summary": "Proxy or redirect MMS media",
        "description": "Authenticated access to message MMS. Twilio-hosted media is proxied with Basic auth. Other HTTPS URLs redirect only when the host is an allowed Vercel Blob origin (team MMS template URLs); unknown hosts return 400. Session cookie auth; optional query teamId for messenger team override. Does not require an active subscription (preview/download only).",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "messageId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "index",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9
            },
            "description": "Zero-based attachment index in message mediaUrls"
          },
          {
            "in": "query",
            "name": "teamId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Messenger team (must be member; calls.view on that team)"
          }
        ],
        "responses": {
          "200": {
            "description": "Media bytes (proxied Twilio) or inline-safe content type",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "302": {
            "description": "Redirect to public HTTPS media URL"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Twilio not configured for message team",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "saveTwilioMessageMediaToCase",
        "summary": "Save MMS image to case documents",
        "description": "Fetches the attachment, validates image content, uploads to blob storage, creates a CaseDocument. Requires calls.view, documents.upload, active subscription, case access, and BLOB_READ_WRITE_TOKEN. When the message has clientId, the case must belong to that client.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "messageId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "index",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9
            }
          },
          {
            "in": "query",
            "name": "teamId",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveTwilioMessageMediaToCaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created document metadata (download via /api/documents/{id}/download)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Blob not configured or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/credentials": {
      "get": {
        "summary": "Get Twilio credentials status",
        "description": "Returns whether Twilio is configured for the billing context of the requested team.",
        "tags": [
          "Twilio",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team to scope credential status to."
          }
        ],
        "responses": {
          "200": {
            "description": "Credential status returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "hasAccountSid": {
                      "type": "boolean"
                    },
                    "hasApiKey": {
                      "type": "boolean"
                    },
                    "billingTeamId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "isBillingTeamContext": {
                      "type": "boolean"
                    },
                    "canManageCredentials": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or no team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - not a member of requested team",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/credentials/voice-push": {
      "get": {
        "summary": "Get Twilio Voice push credential SIDs",
        "description": "Returns FCM and APNs VoIP Push Credential SIDs (CR…) stored for the firm billing team, plus flags when server env fallbacks (TWILIO_VOICE_FCM_PUSH_CREDENTIAL_SID / TWILIO_VOICE_APNS_PUSH_CREDENTIAL_SID) are set. Requires active session team to be the billing team and team.settings.view or team.settings.update. These are Twilio resource SIDs, not FCM registration tokens.",
        "tags": [
          "Twilio",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Push credential SIDs (nullable when unset)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TwilioVoicePushCredentialsResponse"
                }
              }
            }
          },
          "400": {
            "description": "No team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not billing team or insufficient permission to view or update team settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update Twilio Voice push credential SIDs",
        "description": "Set or clear (null) per-firm push credential SIDs. Same auth as saving Twilio account credentials. For automatic FCM creation in Twilio, use POST /api/twilio/credentials/voice-push/provision (server holds Firebase JSON; never accepted in PATCH).",
        "tags": [
          "Twilio",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchTwilioVoicePushCredentialsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated SIDs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TwilioVoicePushCredentialsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not billing team or insufficient permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/credentials/voice-push/sync-notify": {
      "post": {
        "summary": "Sync Voice push SIDs from Twilio Notify credentials",
        "description": "Lists push credentials on the firm Twilio account via the legacy Notify API (notify.twilio.com/v1/Credentials)\nand saves the newest FCM (or GCM) and APNs CR… SIDs on the billing team when missing, or when overwrite flags are set.\nDoes not create credentials in Twilio. Credentials created only via the Comms Push API may not appear in this list.\n",
        "tags": [
          "Twilio",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SyncTwilioVoicePushNotifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SyncTwilioVoicePushNotifyResponse"
                }
              }
            }
          },
          "400": {
            "description": "No team context, Twilio not configured for firm, or validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not billing team or insufficient permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Twilio Notify API or upstream error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/credentials/voice-push/provision": {
      "post": {
        "summary": "Auto-create FCM Voice push credential in Twilio",
        "description": "Uses the firm’s Twilio Account SID + Auth Token and MyLawyerLink’s Firebase service account JSON (server env only)\nto create an FCM credential via Twilio Comms Push Notifications API, then saves the returned CR… on the billing team.\nRequires TWILIO_VOICE_FCM_AUTO_PROVISION_ENABLED and valid TWILIO_FCM_SERVICE_ACCOUNT_JSON_BASE64 (or JSON env).\nTwilio Comms API may require Private Beta access.\n",
        "tags": [
          "Twilio",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProvisionTwilioFcmVoicePushRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Credential created or already present",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProvisionTwilioFcmVoicePushResponse"
                }
              }
            }
          },
          "400": {
            "description": "Twilio not configured for firm",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not billing team or insufficient permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Twilio API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Auto-provision disabled or server missing Firebase JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Twilio FCM credential request timed out (TWILIO_FCM_CREDENTIAL_REQUEST_TIMEOUT)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls": {
      "post": {
        "summary": "Initiate outbound call",
        "description": "Start an outbound call from a team phone number. Optional clientId/caseId to link the call. Use bridgeViaPhone + userPhoneNumber to ring the user's phone first then connect to destination.",
        "tags": [
          "Twilio"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InitiateCallBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Call initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "call": {
                      "$ref": "#/components/schemas/CallSummary"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Phone number or case/client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{id}": {
      "get": {
        "summary": "Get call details",
        "description": "Get call by ID including recording information. Team-scoped.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Call ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Call details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "call": {
                          "$ref": "#/components/schemas/CallDetail"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/transfer/start": {
      "post": {
        "summary": "Start warm transfer (put party on hold)",
        "description": "Puts the remote party (B) and the agent (A) into a conference, then holds B. After this, use transfer/dial to add party C, then transfer/complete to merge B and C.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Twilio Call SID of the active call"
          }
        ],
        "responses": {
          "200": {
            "description": "Transfer started, party on hold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "transferStatus": {
                          "type": "string",
                          "example": "holding"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Twilio not configured or already in transfer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found, no other leg, or a leg ended before redirect (stale state)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Transfer already in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/transfer/dial": {
      "post": {
        "summary": "Dial third party in warm transfer",
        "description": "While transfer is in holding state, dial a number (C). When C answers they join the same conference. Use transfer/complete to merge and leave.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForwardCallBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consult call initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "transferStatus": {
                          "type": "string",
                          "example": "consulting"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or not in holding state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Transfer not in holding state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/transfer/complete": {
      "post": {
        "summary": "Complete warm transfer",
        "description": "Unhold the remote party (B) and remove the agent (A) from the conference so B and C remain connected.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transfer completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Transfer not in consulting state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/transfer/cancel": {
      "post": {
        "summary": "Cancel warm transfer",
        "description": "Hang up the consult leg (C) and unhold the remote party (B). A and B remain in the conference.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transfer cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/forward": {
      "post": {
        "summary": "Forward active call",
        "description": "Forward an active call to another number. Recording continues on the forwarded leg. Use the call's Twilio Call SID (from active call state or POST /api/twilio/calls response).",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Twilio Call SID of the active call"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForwardCallBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Call forwarded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, no team context, or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found or no child leg to forward (outbound)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/calls/{callSid}/end": {
      "post": {
        "summary": "End call",
        "description": "End an active call by Twilio call SID.",
        "tags": [
          "Twilio"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "callSid",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Twilio Call SID"
          }
        ],
        "responses": {
          "200": {
            "description": "Call ended",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No team context or Twilio not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/twilio/call-answering-available": {
      "get": {
        "operationId": "getTwilioCallAnsweringAvailability",
        "summary": "Get Twilio device availability for incoming calls",
        "description": "Returns whether the signed-in user can initialize Twilio Voice devices from any active team membership. Browser incoming-call notifications are delivered via SSE and are not gated by this value.",
        "tags": [
          "Twilio"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Availability resolved for the authenticated user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TwilioCallAnsweringAvailabilityResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Subscription required for all active memberships.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error while checking availability.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/time-entries": {
      "get": {
        "summary": "List time entries",
        "description": "Paginated list of time entries for the team. Use query params limit and offset to request a page. Response includes a standardized pagination object with limit, offset, total, and hasMore. Optional filters caseId, billable, startDate, and endDate.",
        "tags": [
          "Time entries"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "caseId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by case ID."
          },
          {
            "in": "query",
            "name": "billable",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by billable (true/false)."
          },
          {
            "in": "query",
            "name": "startDate",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter entries on or after this date (ISO 8601)."
          },
          {
            "in": "query",
            "name": "endDate",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter entries on or before this date (ISO 8601)."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max items per page (optional; defaults to 50 when omitted)."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of items to skip for pagination (optional)."
          }
        ],
        "responses": {
          "200": {
            "description": "List of time entries for the requested page plus pagination metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timeEntries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TimeEntry"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create time entry",
        "description": "Create a new time entry. Body uses TimeEntryInput schema.",
        "tags": [
          "Time entries"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimeEntryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Time entry created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timeEntry": {
                      "$ref": "#/components/schemas/TimeEntry"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/time-entries/{id}": {
      "get": {
        "summary": "Get time entry",
        "description": "Return a single time entry by ID.",
        "tags": [
          "Time entries"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Time entry detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timeEntry": {
                      "$ref": "#/components/schemas/TimeEntry"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Time entry not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update time entry",
        "description": "Update a time entry by ID.",
        "tags": [
          "Time entries"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TimeEntryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated time entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timeEntry": {
                      "$ref": "#/components/schemas/TimeEntry"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Time entry or case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete time entry",
        "description": "Delete a time entry by ID.",
        "tags": [
          "Time entries"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Time entry deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Time entry not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates": {
      "get": {
        "summary": "List document templates",
        "description": "Get all document templates for the current team. Includes templates from the billing (parent) team when the current team is a child. Each template includes isFromParentTeam when shared from the firm.",
        "tags": [
          "Templates"
        ],
        "responses": {
          "200": {
            "description": "List of templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string",
                            "nullable": true
                          },
                          "isFromParentTeam": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Upload a document template",
        "description": "Upload a new Word document template (.docx) with optional merge fields. The template is associated with the current team. Requires multipart/form-data with file, name, and optional description, category, tags.",
        "tags": [
          "Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "name"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "category": {
                    "type": "string"
                  },
                  "tags": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "template": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (e.g. missing file or name, invalid file type/size)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates/{id}": {
      "get": {
        "summary": "Get a document template",
        "description": "Get a single document template by ID. Template may belong to the current team or the billing (parent) team. Response includes isFromParentTeam when the template is shared from the firm.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Template details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "template": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "isFromParentTeam": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update document template metadata",
        "description": "Update name, description, category, or tags for a template owned by the current team. Cannot update firm (parent) templates from a child team.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentTemplatePatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a document template",
        "description": "Soft-delete a document template by setting isActive to false. Only the team that owns the template can delete it; child teams cannot delete firm (parent) templates.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Template deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (e.g. attempting to delete a firm template from a child team)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates/{id}/preview-fields": {
      "get": {
        "summary": "Preview template merge field values",
        "description": "Get preview of merge field values for a document template based on case and client data. Template resolution uses the active team and its billing (parent) team. Optional query mergeContextTeamId (UUID) overrides which team’s practice/attorney values populate merge fields; it must be a team you belong to (active membership) with the same billing root as the case or client context. Includes stored manual overrides.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          },
          {
            "in": "query",
            "name": "caseId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional case ID; when provided, merge fields use that case's data"
          },
          {
            "in": "query",
            "name": "clientId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client ID"
          },
          {
            "in": "query",
            "name": "mergeContextTeamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional UUID mergeContextTeamId to override the active team context"
          },
          {
            "in": "query",
            "name": "fields",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional comma-separated merge field names (as in template). When provided, response mergeFields are keyed by these names and include alias/AI-resolved values."
          },
          {
            "in": "query",
            "name": "useAI",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "Pass \"true\" to enable AI-based field suggestions for unresolved placeholders (defaults to false when omitted). Initial load typically omits this so only overrides/canonical/aliases are returned."
          },
          {
            "in": "query",
            "name": "mergeCaseSlotOrder",
            "required": false,
            "schema": {
              "type": "array",
              "maxItems": 25,
              "items": {
                "type": "string",
                "format": "uuid"
              }
            },
            "style": "form",
            "explode": true,
            "description": "Repeat this query param (or pass a comma-separated list) with case UUIDs to control `case_1_*`, `case_2_*` order for client-only preview. All IDs must belong to the client; max 25. Ignored when `caseId` is set."
          },
          {
            "in": "query",
            "name": "includeFullCatalog",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "When \"true\" and `fields` is set, the response also includes `catalogMergeFields`, built as `{ ...canonicalMergeFields, ...storedMergeFieldOverrides }` (canonical merge map with per-template stored overrides applied on top) so clients can build sidebars without a second request. No effect when `fields` is omitted (response is already full). Omit the parameter or pass exactly \"true\" or \"false\"; other values return 400."
          }
        ],
        "responses": {
          "200": {
            "description": "Preview merge field key-value pairs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplatePreviewFieldsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (e.g. missing clientId or validation failed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template, case, or client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/api/templates/{id}/generate": {
      "post": {
        "summary": "Generate document from template",
        "description": "Generates a merged document from a template using case and client data. Template lookup stays scoped to the active team and its billing (parent) team. Optional body field mergeContextTeamId (UUID) selects which team’s practice/attorney merge values are used when resolving placeholders; it must be a team you belong to (active membership) and share the same billing root as the case or client context—otherwise the request is rejected. Optional eventId (UUID) scopes event merge fields and requires caseId. Placeholders are resolved via canonical data, alias map, stored overrides, and optional AI suggestions. Client-only generates (no caseId) may send mergeCaseSlotOrder to control which client matters fill case_1_*, case_2_*, … (see TemplateGenerateRequest).",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document generated; returns download URL and document metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateGenerateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request (e.g. client does not match case)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized or not a team member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (insufficient access or subscription)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case, client, template, or event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "408": {
            "description": "Template file fetch timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Merge or validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates/{id}/file": {
      "post": {
        "summary": "Replace document template file",
        "description": "Upload a new Word file for a template owned by the current team. Re-detects merge fields and deletes the previous blob. Child teams cannot replace firm (parent) templates.",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File replaced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentTemplatePatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (e.g. firm template from child team)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates/{id}/download": {
      "get": {
        "summary": "Download document template file",
        "description": "Redirects (302) to the template’s blob URL when the user may access the template (current team or billing parent templates).",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to blob URL"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Template not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/template-attachments/delete": {
      "post": {
        "summary": "Delete unsaved template attachment blobs",
        "description": "Best-effort removal of Vercel Blob URLs for MMS/email attachments that were uploaded in the browser but not yet persisted on a template. URLs must match the team's template-attachment path. Optional body `teamId` (with kind `sms`) uses `calls.view` on that team for message-compose MMS cleanup when the messenger team differs from the session team.",
        "tags": [
          "TemplateAttachments"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateAttachmentDeleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delete attempted; see deletedOk for blob SDK outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateAttachmentDeleteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or one or more URLs were not allowed for this team/kind",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Blob delete reported failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams": {
      "get": {
        "summary": "List user's teams",
        "description": "List all teams the current user belongs to.",
        "tags": [
          "Teams"
        ],
        "responses": {
          "200": {
            "description": "List of teams with membership info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teams": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create team",
        "description": "Create a new team. User becomes OWNER. Subscription limits apply.",
        "tags": [
          "Teams"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "slug": {
                    "type": "string",
                    "maxLength": 100,
                    "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Team created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "$ref": "#/components/schemas/Team"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Team creation limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/merge-sources": {
      "get": {
        "summary": "List merge-source teams for document generation",
        "description": "Returns teams the current user belongs to (active membership) that share the same billing root as the resolved client or case context. Use when choosing mergeContextTeamId for template preview or generate.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "clientId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client context; required if caseId is omitted"
          },
          {
            "in": "query",
            "name": "caseId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case context; when set, billing root follows the case team (clientId should match the case’s client when also provided)"
          }
        ],
        "responses": {
          "200": {
            "description": "Teams allowed as merge sources",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teams": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or client does not match case",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (no access to client or case)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client or case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}": {
      "get": {
        "summary": "Get team",
        "description": "Get team details by ID. Requires team membership and settings view permission.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Team details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "$ref": "#/components/schemas/TeamDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update team",
        "description": "Update team details. Requires team.settings.update permission.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTeam"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Team updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "team": {
                      "$ref": "#/components/schemas/TeamDetail"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete team",
        "description": "Delete a team. Only allowed when team has no active members.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Team deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Cannot delete team - business rule failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or team has members",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/task-automation-rules": {
      "get": {
        "summary": "List task automation rules",
        "description": "Returns all task automation rules for the team. Requires team membership and team.settings.view permission.",
        "tags": [
          "Teams",
          "TaskAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Team ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of task automation rules. When the team has a parent (firm), includes optional inherited rules from the parent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaskAutomationRule"
                      }
                    },
                    "inherited": {
                      "type": "object",
                      "nullable": true,
                      "description": "Present when the team has a parent; contains the parent team's rules that also run for this team.",
                      "properties": {
                        "parentTeam": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            }
                          }
                        },
                        "rules": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TaskAutomationRule"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create task automation rule",
        "description": "Creates a new task automation rule for the team. Requires team.settings.update permission.",
        "tags": [
          "Teams",
          "TaskAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Team ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Conditional validation (taskAutomationRuleSchema superRefine). case_event_created requires at least one triggerEventTypes entry. case_status_change requires at least one triggerCaseStatuses entry. case_custom_field_change, client_custom_field_change, and case_event_custom_field_change require at least one triggerCustomFieldNames entry (field definition names). Optional triggerCustomFieldTargetValues — when non-empty, the rule runs only when a watched field's new value matches one of these strings after normalization; empty or omitted means any change to the selected fields. client_created and client_assigned_to_team need no extra trigger fields. Billing cron triggers require triggerDaysAfterDue with trigger-specific ranges (see OpenAPI TaskAutomationRule.triggerDaysAfterDue); evaluated by the daily billing task-automation cron.\n",
                "required": [
                  "taskTitle",
                  "daysOffset"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "triggerType": {
                    "type": "string",
                    "enum": [
                      "case_event_created",
                      "case_status_change",
                      "case_custom_field_change",
                      "client_custom_field_change",
                      "case_event_custom_field_change",
                      "client_created",
                      "client_assigned_to_team",
                      "invoice_overdue",
                      "payment_plan_installment_overdue",
                      "invoice_due_relative_before",
                      "invoice_due_relative_after",
                      "payment_plan_installment_due_relative_before",
                      "payment_plan_installment_due_relative_after"
                    ],
                    "default": "case_event_created",
                    "description": "Determines required companion fields; see route description."
                  },
                  "triggerEventTypes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "court_date",
                        "deadline",
                        "meeting",
                        "communication",
                        "filing",
                        "document",
                        "billing",
                        "task",
                        "drive_time",
                        "trial",
                        "other"
                      ]
                    },
                    "minItems": 0
                  },
                  "triggerCaseStatuses": {
                    "type": "array",
                    "nullable": true,
                    "items": {
                      "type": "string",
                      "maxLength": 64
                    },
                    "description": "When triggerType is case_status_change",
                    "entries must be firm-configured case statuses (validated at runtime).": null
                  },
                  "triggerCustomFieldNames": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "triggerCustomFieldTargetValues": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "triggerDaysAfterDue": {
                    "type": "integer",
                    "minimum": -365,
                    "maximum": 365,
                    "nullable": true,
                    "description": "Required for billing cron triggerTypes; semantics depend on trigger (see OpenAPI TaskAutomationRule)."
                  },
                  "daysOffset": {
                    "type": "integer",
                    "minimum": -365,
                    "maximum": 365,
                    "description": "Required. Days before (negative) or after (positive) the trigger."
                  },
                  "taskTitle": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "taskDescription": {
                    "type": "string",
                    "maxLength": 5000,
                    "nullable": true
                  },
                  "assignToTeamMemberId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  },
                  "usePreviousFridayIfWeekendOrMonday": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true",
                    "if the task due date would fall on Saturday": null,
                    "Sunday": null,
                    "or Monday in the case team's IANA timezone (Team.timezone": null,
                    "not UTC)": null,
                    "it is set to the previous Friday in that same local sense.": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rule created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TaskAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/task-automation-rules/{ruleId}": {
      "get": {
        "summary": "Get task automation rule",
        "description": "Returns a single task automation rule by id. Requires team membership and team.settings.view permission.",
        "tags": [
          "Teams",
          "TaskAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Team ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "description": "Rule ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task automation rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TaskAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team or rule not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update task automation rule",
        "description": "Updates an existing task automation rule. Requires team.settings.update permission.",
        "tags": [
          "Teams",
          "TaskAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Team ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "description": "Rule ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskAutomationRuleUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rule updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TaskAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Rule not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      },
      "delete": {
        "summary": "Delete task automation rule",
        "description": "Deletes a task automation rule. Requires team.settings.update permission. Existing tasks created by the rule are not removed.",
        "tags": [
          "Teams",
          "TaskAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Team ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "description": "Rule ID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rule deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Rule not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/teams/{id}/switch": {
      "post": {
        "summary": "Switch active team",
        "description": "Set the given team as the active team for the current session (cookie).",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Switched",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "team": {
                      "$ref": "#/components/schemas/TeamWithRole"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not a member of this team",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/members": {
      "get": {
        "summary": "List team members",
        "description": "List team members with optional search and pagination.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Search term (filters by username or email)"
          },
          {
            "in": "query",
            "name": "skip",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Number of members to skip (for pagination)"
          },
          {
            "in": "query",
            "name": "take",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Maximum number of members to return"
          }
        ],
        "responses": {
          "200": {
            "description": "List of members with pagination info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamMember"
                      }
                    },
                    "totalCount": {
                      "type": "integer",
                      "description": "Total number of members matching the request"
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True if more results are available"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Invite team member",
        "description": "Invite a user by email to the team with a role.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MANAGER",
                      "MEMBER",
                      "LAWYER",
                      "READ_ONLY"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation created, or user auto-added if they already had an account",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/InviteMemberInviteResponse"
                    },
                    {
                      "$ref": "#/components/schemas/InviteMemberAutoAddResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or no seats",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/members/{memberId}": {
      "put": {
        "summary": "Update team member",
        "description": "Update a team member's role or status.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "memberId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MANAGER",
                      "MEMBER",
                      "LAWYER",
                      "READ_ONLY"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ACTIVE",
                      "SUSPENDED",
                      "DEACTIVATED"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Member updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "member": {
                      "$ref": "#/components/schemas/TeamMember"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove team member",
        "description": "Remove a member from the team.",
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "memberId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Member removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team or member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/ai-knowledge-automation-rules": {
      "get": {
        "summary": "List AI knowledge automation rules",
        "description": "Returns digest rules for the team. Requires team.settings.view.",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AiKnowledgeAutomationRule"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Team not found"
          }
        }
      },
      "post": {
        "summary": "Create AI knowledge automation rule",
        "description": "Requires team.settings.update. runAsTeamMemberId must be an active member of the team.",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiKnowledgeAutomationRuleCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AiKnowledgeAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Team not found"
          },
          "500": {
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/api/teams/{id}/ai-knowledge-automation-rules/writable-parent-pages": {
      "get": {
        "summary": "List wiki parent pages writable by run-as member (AI digest)",
        "description": "Requires team.settings.update. Query runAsTeamMemberId must be an active team member with knowledge.edit.",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "runAsTeamMemberId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Optional title filter (contains, case-insensitive)"
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Omit with offset to return the full sorted list"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 50000,
              "description": "Omit with limit to return the full sorted list"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiKnowledgeAutomationWritableParentPagesResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenResponse"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/teams/{id}/ai-knowledge-automation-rules/{ruleId}": {
      "get": {
        "summary": "Get AI knowledge automation rule",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AiKnowledgeAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Internal Server Error"
          }
        }
      },
      "put": {
        "summary": "Update AI knowledge automation rule",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiKnowledgeAutomationRuleUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AiKnowledgeAutomationRule"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Rule not found"
          },
          "500": {
            "description": "Internal Server Error"
          }
        }
      },
      "delete": {
        "summary": "Delete AI knowledge automation rule",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Rule not found"
          },
          "500": {
            "description": "Internal Server Error"
          }
        }
      }
    },
    "/api/teams/{id}/ai-knowledge-automation-rules/{ruleId}/run": {
      "post": {
        "summary": "Run AI knowledge digest rule now",
        "description": "Builds context, calls the model, creates a Knowledge page. Requires team.settings.update.",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "pageId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "runId": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Run failed"
          }
        }
      }
    },
    "/api/teams/{id}/ai-knowledge-automation-rules/{ruleId}/preview": {
      "post": {
        "summary": "Preview AI knowledge digest (no wiki page)",
        "description": "Runs the same data + model as a real run; returns markdown and title preview only. Requires team.settings.update.",
        "tags": [
          "Teams",
          "AiKnowledgeAutomation"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "ruleId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preview generated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiKnowledgeAutomationPreviewResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or preview generation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenResponse"
                }
              }
            }
          },
          "404": {
            "description": "Rule not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/stripe/create-portal": {
      "post": {
        "summary": "Create Stripe customer portal session",
        "description": "Requires Permission['team.subscription.manage'] — granted to owners/admins and via explicit per-member grants; uses the team's Stripe customer ID.",
        "tags": [
          "Stripe"
        ],
        "responses": {
          "200": {
            "description": "Portal session URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No active subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Missing team.subscription.manage permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/stripe/create-checkout": {
      "post": {
        "summary": "Create Stripe checkout session",
        "description": "Create a Stripe Checkout session for subscription (tier, billingPeriod). Seat count is derived from active team members.",
        "tags": [
          "Stripe"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tier",
                  "billingPeriod"
                ],
                "properties": {
                  "tier": {
                    "type": "string",
                    "enum": [
                      "basic",
                      "pro"
                    ]
                  },
                  "billingPeriod": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "annual"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout session URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Must be team member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/sms-templates": {
      "get": {
        "summary": "List SMS templates",
        "description": "Returns SMS templates for the current team (or optional query teamId), including templates from the billing (parent) team when that team is a child. Each template includes isFromParentTeam when shared from the firm. Requires team membership and clients.view on the resolved team.",
        "tags": [
          "SMS Templates"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team to list templates for; must be member with clients.view (e.g. messenger team tab)."
          }
        ],
        "responses": {
          "200": {
            "description": "List of templates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplatesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (invalid teamId query)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create SMS template",
        "description": "Create a new SMS template for the current team. Requires team membership, sms_templates.manage permission, and active subscription.",
        "tags": [
          "SMS Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsTemplateCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateSingleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/sms-templates/upload-handler": {
      "post": {
        "summary": "Vercel Blob client upload callback for SMS template MMS attachments",
        "description": "JSON body from @vercel/blob/client handleUpload (token generation or upload completed). Requires sms_templates.manage.",
        "tags": [
          "SMS Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlobClientUploadCallbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload token payload or completion acknowledgement",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BlobClientUploadTokenResponse"
                    },
                    {
                      "$ref": "#/components/schemas/BlobClientUploadCompletedAckResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Blob storage not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/sms-templates/resolve": {
      "post": {
        "summary": "Resolve SMS template body",
        "description": "Resolve merge fields in an SMS template body using optional client/case/event context. Case and event data use the full firm team tree (billing root and descendant offices) so sibling-office matters merge correctly. The template row is resolved from the requested team (body teamId) or session team or its billing parent. Optional body teamId scopes merge when the caller is a member with clients.view on that team. When eventId is provided, only non-completed case events are accepted. Requires resource access for any provided IDs.",
        "tags": [
          "SMS Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsTemplateResolveRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolved body and merge field map",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SmsTemplateResolveSuccessData"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Template not found"
          },
          "500": {
            "description": "Failed to resolve template"
          }
        }
      }
    },
    "/api/sms-templates/context": {
      "get": {
        "summary": "Get SMS template context options",
        "description": "Returns client cases under the same firm (billing root and all child office teams), non-completed calendar events (tasks excluded), and custom case/event field names for template context. Matches the case list scope used elsewhere for firm clients (e.g. client detail).",
        "tags": [
          "SMS Templates"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "clientId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team for custom field catalog scope; must be member with clients.view (e.g. messenger team tab)."
          }
        ],
        "responses": {
          "200": {
            "description": "Context payload for template case/event selection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateContextResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (e.g. invalid or missing clientId)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized or not a team member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (missing clients.view or insufficient access to this client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found for this team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to load template context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/sms-templates/{id}": {
      "patch": {
        "summary": "Update SMS template",
        "description": "Update an SMS template by id. Only the team that owns the template can update it; child teams cannot edit firm (parent) templates.",
        "tags": [
          "SMS Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1
                  },
                  "category": {
                    "type": "string",
                    "nullable": true
                  },
                  "attachmentsJson": {
                    "type": "array",
                    "nullable": true,
                    "description": "MMS attachments (Vercel Blob URLs); omit to leave unchanged, null or [] to clear",
                    "maxItems": 5,
                    "items": {
                      "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template updated"
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (e.g. attempting to edit a firm template from a child team)"
          },
          "404": {
            "description": "Template not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      },
      "delete": {
        "summary": "Delete SMS template",
        "description": "Delete an SMS template by id. Only the team that owns the template can delete it; child teams cannot delete firm (parent) templates. When case events reference this template, the request body must include replacementTemplateId (another template on the same team) so those events keep their SMS settings. The server re-reads affected events inside a single transaction before mutating.",
        "tags": [
          "SMS Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Template ID"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsTemplateDeleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateDeleteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or replacement template required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateDeleteErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden (e.g. attempting to delete a firm template from a child team)"
          },
          "404": {
            "description": "Template not found"
          },
          "409": {
            "description": "Delete could not complete because references changed during the operation; refresh and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateDeleteErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/sms-templates/{id}/delete-preview": {
      "get": {
        "summary": "Preview SMS template delete impact",
        "description": "Returns how many case events reference this template and which other templates on the same team can be chosen as replacements when deleting.",
        "tags": [
          "SMS Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preview payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsTemplateDeletePreview"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Template not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "summary": "Global search (clients and cases)",
        "description": "Single round-trip for Cmd/Ctrl+F search. Returns `clients` and `cases` collections, each with `items` and `pagination` metadata. The same `limit`/`offset` query params are applied to both collections.",
        "tags": [
          "Clients",
          "Cases"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching clients and cases with per-collection pagination metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GlobalSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/portal/invoices": {
      "get": {
        "summary": "List portal invoices",
        "description": "Returns invoices for the authenticated portal user’s current client and team, including billingMode (standard vs retainer_statement).",
        "tags": [
          "Portal"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Invoices for the client",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invoices"
                  ],
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PortalInvoiceListItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (e.g. missing client or team context)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invoice viewing disabled for the portal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/portal/documents/{id}/download": {
      "get": {
        "summary": "Download portal client document",
        "description": "Streams a file the client uploaded via the portal. Requires an authenticated portal session, current client context, and document uploads enabled for the firm.",
        "tags": [
          "Portal",
          "Portal Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File binary",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or missing client context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Document upload disabled for portal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Download timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/portal/case-documents/{id}/download": {
      "get": {
        "summary": "Download case document (portal)",
        "description": "Streams a case document for the signed-in portal user when the file is stored as a private blob. Requires portal session and client/team context; document must belong to the client’s case on the current team.",
        "tags": [
          "Portal",
          "Portal Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File binary",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or missing client/team context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid document type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Blob storage not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Download timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-plans": {
      "get": {
        "summary": "List payment plans",
        "description": "Payment plans for the active team. Optional filters by case, client, invoice, or status.",
        "tags": [
          "Payment plans"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "caseId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by primary case id."
          },
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by client id (access-checked when set)."
          },
          {
            "in": "query",
            "name": "invoiceId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by linked invoice id."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "completed",
                "cancelled"
              ]
            },
            "description": "Filter by plan status."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Max plans to return."
          }
        ],
        "responses": {
          "200": {
            "description": "Payment plans for the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paymentPlans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentPlan"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create payment plan",
        "description": "Creates a payment plan and installments linked to an invoice. Optional sendEmail notifies the client using firm billing sender and templates when allowed.",
        "tags": [
          "Payment plans"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentPlanCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created payment plan; `paymentPlan.items` matches the PaymentPlan schema items array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paymentPlan": {
                      "$ref": "#/components/schemas/PaymentPlan"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation or business rule failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice or related resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-plans/{id}": {
      "get": {
        "summary": "Get payment plan",
        "description": "Return a single payment plan by ID.",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment plan detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paymentPlan": {
                      "$ref": "#/components/schemas/PaymentPlan"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Payment plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update payment plan",
        "description": "Update a payment plan by ID. When replacing `items`, the sum of installment principals cannot exceed the linked invoice total (and the linked invoice must be loadable for validation).",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentPlanUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated payment plan.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paymentPlan": {
                      "$ref": "#/components/schemas/PaymentPlan"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Payment plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete payment plan",
        "description": "Delete a payment plan by ID. Items are cascade deleted.",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment plan deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Payment plan not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-plans/{id}/send-email": {
      "post": {
        "summary": "Send payment plan email to client",
        "description": "Sends the payment plan details to the client's email address.",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email sent successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Client has no email address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Payment plan or team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to send email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-plans/{id}/items/{itemId}": {
      "put": {
        "summary": "Update payment plan installment",
        "description": "Update installment status, due date, principal amount, late fee, optional late fee assessed date, or payment metadata. Each row must have principal and/or late fee greater than zero. After changing `amount`, the server sets `PaymentPlan.totalAmount` to the sum of installment principals (late fees excluded). When the plan is linked to an invoice, that sum cannot exceed the linked invoice total. Paid installments can be edited the same way (e.g. correct amount or late fee after recording payment).",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "itemId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentPlanItemUpdateInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated installment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/PaymentPlanItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request body validation failed (Zod), mark-paid transition not allowed (plan inactive or installment not open), installment business rules failed (e.g. zero-value row, invalid late-fee assessed date), or linked-invoice checks failed after a principal `amount` change: total installment principal would exceed the linked invoice total, or the linked invoice could not be loaded for validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Plan or item not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete payment plan installment",
        "description": "Remove an unpaid installment from an active plan (e.g. client paid off early). Paid installments cannot be deleted. At least one installment must remain, and at least one other principal-bearing row must remain when deleting a principal row. Plan totalAmount is recalculated from remaining principals; plan status may become completed when no open installments remain.",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "itemId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentPlanItemDeleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Installment removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentPlanItemDeleteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Plan not active, paid row, last installment / last principal row, invalid body, or invoice sync rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Plan or item not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/payment-plans/{id}/items/{itemId}/generate-invoice": {
      "post": {
        "summary": "Generate draft invoice for a payment plan installment",
        "description": "Creates a draft invoice for one installment (principal and optional per-installment late fee). The draft includes a readable summary of the full payment schedule (paid and upcoming rows); only the current installment is billed. Rejects installments that are paid, cancelled, or skipped. If a draft already exists for this installment, it is refreshed from the current plan (schedule, notes, and line items) so paid installments stay in sync.",
        "tags": [
          "Payment Plans"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Payment plan id"
          },
          {
            "in": "path",
            "name": "itemId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Payment plan installment id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyJsonObjectRequestBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentPlanItemGenerateInvoiceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Installment not billable or bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Payment plan or installment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Could not allocate a unique invoice number after retries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/notifications": {
      "get": {
        "summary": "List notifications",
        "description": "Paginated list of notifications for the current user. Uses limit (default 50, max 100) and offset (default 0). Response includes a standardized pagination object with limit, offset, total, and hasMore. Optional unreadOnly filter.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max items per page (default 50)."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            },
            "description": "Number of items to skip (default 0)."
          },
          {
            "in": "query",
            "name": "unreadOnly",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "When true, return only unread notifications."
          },
          {
            "in": "query",
            "name": "scope",
            "schema": {
              "type": "string",
              "enum": [
                "current_team",
                "firm"
              ],
              "default": "current_team"
            },
            "description": "When firm and current team is the parent, return notifications for all teams in the firm; otherwise current team only."
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string"
            },
            "description": "Filter by single notification type (e.g. task_assigned)."
          },
          {
            "in": "query",
            "name": "types",
            "schema": {
              "type": "string"
            },
            "description": "Filter by comma-separated notification types."
          }
        ],
        "responses": {
          "200": {
            "description": "Notifications for the requested page plus pagination metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "notifications": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NotificationListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (query params limit/offset/unreadOnly)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/notifications/unread-count": {
      "get": {
        "summary": "Get unread notification count",
        "description": "Return the unread notification count for the current user. Returns zero when there is no active team context. Optional scope (firm) and type/types filter.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ScopeParam"
          },
          {
            "$ref": "#/components/parameters/TypeParam"
          },
          {
            "$ref": "#/components/parameters/TypesParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Unread notification count.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationUnreadCountResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (invalid query parameters)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/notifications/mark-all-read": {
      "post": {
        "summary": "Mark all notifications read",
        "description": "Mark all unread notifications as read for the current user. Returns zero when there is no active team context.",
        "tags": [
          "Notifications"
        ],
        "responses": {
          "200": {
            "description": "Notifications marked as read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationMarkAllReadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/notifications/{id}": {
      "patch": {
        "summary": "Mark notification read",
        "description": "Mark a specific notification as read.",
        "tags": [
          "Notifications"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notification updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationReadResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Notification not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/knowledge/pages": {
      "get": {
        "summary": "List knowledge pages",
        "description": "Lists wiki pages visible to the current team (firm-wide and team-scoped). Optional q runs full-text search.",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Full-text search query"
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "description": "Max 50 when using search (q); max 500 for full list"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Skip N rows when listing without search (pagination for large wikis)"
          },
          {
            "in": "query",
            "name": "tag",
            "schema": {
              "type": "string"
            },
            "description": "Filter to pages that include this tag (normalized)"
          }
        ],
        "responses": {
          "200": {
            "description": "List of pages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Server error"
          }
        }
      },
      "post": {
        "summary": "Create knowledge page",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgePageCreateRequest"
              }
            }
          }
        },
        "tags": [
          "Knowledge"
        ],
        "responses": {
          "200": {
            "description": "Created page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/knowledge/pages/{id}": {
      "get": {
        "summary": "Get knowledge page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update knowledge page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgePagePatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Revision conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete knowledge page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "ok"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/knowledge/pages/{id}/versions": {
      "get": {
        "summary": "List knowledge page versions",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageVersionListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Save a version snapshot of the page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgePageVersionCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageVersionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/knowledge/pages/{id}/lock": {
      "post": {
        "summary": "Lock or unlock a knowledge page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgePageLockRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Conflict"
          },
          "500": {
            "description": "Internal server error"
          }
        }
      }
    },
    "/api/knowledge/pages/{id}/images": {
      "post": {
        "summary": "Upload image for knowledge page embed",
        "description": "Multipart form field `file`. Returns a public blob URL to use in editor content.",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgePageImageUploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/knowledge/pages/{id}/comments": {
      "get": {
        "summary": "List comments on a knowledge page",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeCommentListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add comment",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeCommentCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeCommentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/knowledge/comments/{commentId}": {
      "patch": {
        "summary": "Update own knowledge comment (or managers)",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "commentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeCommentPatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeCommentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete knowledge comment",
        "tags": [
          "Knowledge"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "commentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteKnowledgeCommentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices": {
      "get": {
        "summary": "List invoices",
        "description": "Paginated list of invoices for the team. Use query params limit and offset to request a page. Response includes a standardized pagination object with limit, offset, total, and hasMore. Optional filters caseId and status.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "caseId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by case ID."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "paid",
                "overdue",
                "cancelled"
              ]
            },
            "description": "Filter by invoice status."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max items per page (optional; defaults to 50 when omitted)."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of items to skip for pagination (optional)."
          }
        ],
        "responses": {
          "200": {
            "description": "List of invoices for the requested page plus pagination metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create invoice",
        "description": "Create a new invoice. Body uses InvoiceInput schema.",
        "tags": [
          "Invoices"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "invoice": {
                          "$ref": "#/components/schemas/Invoice"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Duplicate invoice number for this team (unique teamId + invoiceNumber). Body matches ErrorResponse; error text explains that another invoice already uses this number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/generate-number": {
      "get": {
        "summary": "Generate next invoice number",
        "description": "Returns the next invoice number for the current team. Requires team.billing.edit permission.",
        "tags": [
          "Invoices"
        ],
        "responses": {
          "200": {
            "description": "Next invoice number",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoiceNumber": {
                      "type": "string",
                      "example": "INV-2025-001"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}": {
      "get": {
        "summary": "Get invoice",
        "description": "Return a single invoice by ID. Resolution is **firm-scoped**: the billing root for the active team is derived (`TeamRepository.getBillingTeamId`), then the invoice must belong to a team in `getFirmTeamIds` for that root (covers legacy invoices stored on child practice teams). Invoices outside that firm tree return **404**.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoice": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update invoice (metadata vs billing + payment plans)",
        "description": "Partial update of invoice fields. The target row is resolved with **firm-scoped billing lookup** (billing root plus `getFirmTeamIds`, same as GET): invoices outside the active firm’s tree return **404**. The transactional write re-checks `id`, `teamId ∈ firmTeamIds`, and the **same `clientId` / `caseId`** as at authorization time so a concurrent reassignment cannot apply the update to a moved row. Replacing `items` recalculates subtotal/tax/total from lines. Sending `total` without `items` scales existing line items proportionally to match the target total. **`InvoiceUpdate` / `updateInvoiceSchema`:** Do not send `paymentPlanSync` together with `items` — validation returns 400 (`Do not send paymentPlanSync when replacing line items.`). Raising principal via new line items while appending plan installments is not supported on this endpoint; use billing-only fields (`total`, `taxRate`, `discountAmount`) with `paymentPlanSync` when required. **Linked payment plan (non-cancelled):** If the body includes any of `total`, `taxRate`, or `discountAmount` (without `items`) and the new invoice total exceeds installment principal, send `paymentPlanSync` with `mode: appendPrincipal` and `dueDate`. **Metadata-only updates** (e.g. `status`, `dueDate`, `notes`, `paidAt` without `items`, `total`, `taxRate`, or `discountAmount`) do **not** require `paymentPlanSync` when historical drift makes invoice total higher than principal. Sending `paymentPlanSync` when the invoice has no linked plan returns 400. Cancelled plans never require `paymentPlanSync` for total changes.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceUpdate"
              },
              "examples": {
                "metadataOnly": {
                  "summary": "Status / due date only (linked plan, no billing fields)",
                  "description": "No paymentPlanSync even if invoice total and plan principal are out of sync from historical drift.",
                  "value": {
                    "status": "draft",
                    "dueDate": "2026-04-07T17:00:00.000Z"
                  }
                },
                "increaseTotalWithPlan": {
                  "summary": "Raise total with appendPrincipal (non-cancelled plan)",
                  "value": {
                    "total": 105,
                    "paymentPlanSync": {
                      "mode": "appendPrincipal",
                      "dueDate": "2026-06-01T17:00:00.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated invoice (including metadata-only saves without paymentPlanSync when applicable).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoice": {
                      "$ref": "#/components/schemas/Invoice"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (`updateInvoiceSchema` / `InvoiceUpdate`), or business rule rejection — e.g. `items` with `paymentPlanSync`, `paymentPlanSync` without a linked plan, increasing total above installment principal on a non-cancelled plan without `paymentPlanSync`, invalid `paymentPlanSync.dueDate`, or conflicting fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete invoice",
        "description": "Delete an invoice by ID using **firm-scoped billing lookup** (billing root + `getFirmTeamIds`); invoices outside the firm tree return **404**. The delete is applied only when the row still matches `id`, `teamId ∈ firmTeamIds`, and the **same `clientId` / `caseId`** as authorized, so a concurrent reassignment yields **404** instead of deleting the wrong scope.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invoice deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/send-email": {
      "post": {
        "summary": "Send invoice email",
        "description": "Send the invoice to the client by email.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email sent successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing client email or sender email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/invoices/{id}/download": {
      "get": {
        "summary": "Download invoice document",
        "description": "Generate and download an invoice as a DOCX document.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "DOCX invoice document.",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Invoice not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/events": {
      "post": {
        "summary": "Create firm event",
        "description": "Create a case event for a case in the firm. Requires firm view and active subscription. Case must belong to the firm. Body requires caseId and event fields (eventType, title, eventDate, etc.).",
        "tags": [
          "Firm Events"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FirmEventCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "event": {
                          "$ref": "#/components/schemas/CaseEvent"
                        },
                        "smsStatus": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "skipped",
                                "failed"
                              ]
                            },
                            "reason": {
                              "type": "string",
                              "enum": [
                                "no_phone",
                                "no_twilio",
                                "no_sender"
                              ]
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found or not in firm",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/events/{id}": {
      "patch": {
        "summary": "Mark firm event complete",
        "description": "Mark a case event as completed. Event's case must belong to the firm.",
        "tags": [
          "Firm Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event marked complete",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "event": {
                          "$ref": "#/components/schemas/CaseEvent"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete firm event",
        "description": "Delete a case event. Event's case must belong to the firm.",
        "tags": [
          "Firm Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update firm event",
        "description": "Update a case event. Event's case must belong to the firm. Partial update; only provided fields are updated.",
        "tags": [
          "Firm Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseEventUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "event": {
                          "$ref": "#/components/schemas/CaseEvent"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/emails/threads": {
      "get": {
        "summary": "List firm email threads",
        "description": "List email threads across the firm (parent and child teams). Requires firm view. Supports pagination, search, and optional teamId filter.",
        "tags": [
          "Emails",
          "Firm"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Page size (max 100)"
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of records to skip"
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string"
            },
            "description": "Search by subject or participants"
          },
          {
            "in": "query",
            "name": "teamId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by team (must be in firm)"
          }
        ],
        "responses": {
          "200": {
            "description": "List of threads and pagination",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "threads": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmailThread"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/court-reports/by-case-number": {
      "get": {
        "summary": "List court filing reports by case number (firm intake)",
        "description": "For Utah Legal Group only: search `Reports` by numeric case number for new-client intake. Requires firm view and `clients.create` on the billing team. Optional `sinceDaysAgo` limits to filings on or after that many days ago (UTC calendar day start).\n",
        "tags": [
          "Firm Clients"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "caseNumber",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Numeric court report case number"
          },
          {
            "in": "query",
            "name": "sinceDaysAgo",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1825
            },
            "description": "Optional; only returns rows with filing_date on or after this many days ago"
          }
        ],
        "responses": {
          "200": {
            "description": "Matches (may be empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "matches"
                      ],
                      "properties": {
                        "matches": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/FirmCourtReportMatch"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/clients": {
      "get": {
        "summary": "List firm clients",
        "description": "List all clients across the firm (parent and child teams). Requires firm view permission. Supports pagination, search, team filter (teamId = child team UUID or \"unassigned\"), open cases only (open=1 or true), and sort/dir.",
        "tags": [
          "Firm Clients"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string"
            },
            "description": "Search by name, email, or phone"
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Page size (default 50)"
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of records to skip"
          },
          {
            "in": "query",
            "name": "teamId",
            "schema": {
              "type": "string"
            },
            "description": "Filter by team. Use a child team UUID to show only clients assigned to that team, or \"unassigned\" for clients in the firm with no child-team assignment. Omit for all firm clients."
          },
          {
            "in": "query",
            "name": "open",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true",
                "0",
                "false"
              ]
            },
            "description": "Open cases filter. Truthy (1 or true, case-insensitive) returns only clients with at least one open case; falsy (0 or false, case-insensitive) returns all clients. Omit for all clients. Matches getFirmClientsQuerySchema validation."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "alphabetical",
                "name",
                "email",
                "created",
                "updated",
                "recently_added",
                "recently_updated",
                "team",
                "cases",
                "open_cases"
              ]
            },
            "description": "Sort field. Default name"
          },
          {
            "in": "query",
            "name": "dir",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "description": "Sort direction. Default asc"
          }
        ],
        "responses": {
          "200": {
            "description": "List of clients and pagination",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "clients": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Client"
                          }
                        },
                        "pagination": {
                          "$ref": "#/components/schemas/PaginationMetadata"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (no firm view)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create firm client",
        "description": "Create a client and assign to a team in the firm (parent or child). Body includes client fields and optional teamId. Requires firm view and active subscription.",
        "tags": [
          "Firm Clients"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ClientInput"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "teamId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Optional; assign client to this firm or child team. Defaults to firm."
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "client": {
                          "$ref": "#/components/schemas/Client"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, team invalid, or missing required data after validation (Prisma P2011 null constraint)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "validation": {
                    "summary": "Zod validation or invalid team",
                    "value": {
                      "error": "Validation failed",
                      "details": {}
                    }
                  },
                  "nullConstraint": {
                    "summary": "Required field missing at save (P2011)",
                    "value": {
                      "error": "Cannot save because a required field must not be empty. Check required fields (for example phone numbers and labels) and try again."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/clients/{id}/assign-team": {
      "get": {
        "summary": "Preview assign/unassign impact on calendar events",
        "description": "Returns how many case event assignees and SMS template links will remap or clear when transferring cases between the firm and a child team. Requires firm view and the same team rules as POST/DELETE.",
        "tags": [
          "Firm Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "preview",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "in": "query",
            "name": "teamId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "mode",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "assign",
                "unassign"
              ]
            }
          },
          {
            "in": "query",
            "name": "includeChoices",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "When 1, response includes eventBreakdown, destinationTeamMembers, destinationSmsTemplates, destinationEmailTemplates for UI overrides"
          }
        ],
        "responses": {
          "200": {
            "description": "Preview payload (impact counts; with includeChoices=1, breakdown and pick lists)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmClientAssignTeamPreviewResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid preview request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Assign client to child team",
        "description": "Assign a client (who lives in the parent/firm team) to a child team for visibility and case management. Client stays in parent; creates a ClientFirmAssignment. Optional eventOverrides remap task assignees and per-event SMS templates on the destination child team. Requires firm view. Client must be in parent team; teamId must be a child of the firm.",
        "tags": [
          "Firm Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FirmClientAssignTeamRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assigned (or already assigned)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmClientAssignTeamResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or client not in parent or team not a child",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (no firm view or permission on target team)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Unassign client from child team",
        "description": "Remove the assignment of a client from a child team. Client stays in parent. This client's cases in the child team are transferred back to the parent so client and cases stay together. Requires firm view.",
        "tags": [
          "Firm Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client ID"
          },
          {
            "in": "query",
            "name": "teamId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Child team to unassign from"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FirmClientUnassignTeamRequestBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unassigned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmClientUnassignTeamResponse"
                }
              }
            }
          },
          "400": {
            "description": "teamId missing or invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client or assignment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/cases": {
      "post": {
        "summary": "Create firm case",
        "description": "Create a case and assign it to a team in the firm (parent or child). Requires firm view and cases.create permission. Client must belong to the selected team. Title is server-generated (format LastName, FirstName - caseNumber - count); when caseNumber is missing, an em dash (—) is used.",
        "tags": [
          "Firm Cases"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/CaseInput"
                  },
                  {
                    "type": "object",
                    "required": [
                      "teamId"
                    ],
                    "properties": {
                      "teamId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Team to assign the case to (parent or child)"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Case created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Case"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or team/client invalid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (no firm view or cases.create)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/firm/audit": {
      "get": {
        "summary": "List firm audit logs",
        "description": "List audit logs across the firm (parent and child teams). Requires firm view and active subscription.",
        "tags": [
          "Audit"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Page size"
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of records to skip"
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string"
            },
            "description": "Search in action, entity, error message"
          }
        ],
        "responses": {
          "200": {
            "description": "List of audit logs and pagination",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "logs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AuditLog"
                          }
                        },
                        "pagination": {
                          "$ref": "#/components/schemas/PaginationMetadata"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/events": {
      "get": {
        "summary": "List upcoming events",
        "description": "List upcoming case events for the current team. Query uses limit plus days (date window) rather than offset-based pagination. Response still includes the standardized pagination object with limit, offset, total, and hasMore; offset is always 0 for this endpoint.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Max number of events to return (optional)."
          },
          {
            "in": "query",
            "name": "days",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Number of days ahead to include (optional; restricts the date window)."
          }
        ],
        "responses": {
          "200": {
            "description": "List of events in the requested window plus standardized pagination metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseEvent"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/events/{id}": {
      "put": {
        "summary": "Update event (partial)",
        "description": "Update an event. Only provided fields are updated (patch-style). Event's case must be visible to the current team (including parent-team cases when the client is assigned to this child team).",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseEventUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "event": {
                          "$ref": "#/components/schemas/CaseEvent"
                        },
                        "smsStatus": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "skipped",
                                "failed"
                              ]
                            },
                            "reason": {
                              "type": "string",
                              "enum": [
                                "no_phone",
                                "no_twilio",
                                "no_sender"
                              ]
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete event",
        "description": "Delete a case event. Event's case must be visible to the current team (including parent-team cases when the client is assigned to this child team).",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Mark event as completed",
        "description": "Mark a case event as completed. Event's case must be visible to the current team (including parent-team cases when the client is assigned to this child team).",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event marked as completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "event": {
                          "$ref": "#/components/schemas/CaseEvent"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/threads": {
      "get": {
        "summary": "List email threads",
        "description": "Paginated email thread list for the current team with optional search, client, case, and archive filters.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "caseId",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "isArchived",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email thread page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailThreadsListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/threads/{threadId}": {
      "get": {
        "summary": "Get email thread",
        "description": "Return a single email thread and all emails in that thread.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "threadId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email thread detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "thread": {
                      "$ref": "#/components/schemas/EmailThread"
                    },
                    "emails": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmailItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Thread not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update email thread",
        "description": "Update thread association or state, including archive, star, and read status.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "threadId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailThreadUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated thread.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "thread": {
                      "$ref": "#/components/schemas/EmailThread"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Thread not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/status/{resendEmailId}": {
      "get": {
        "summary": "Get email delivery status",
        "description": "Fetch current delivery status for a sent email from Resend, with fallback to stored status.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "resendEmailId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmailStatusItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid email ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Email not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/send": {
      "post": {
        "summary": "Send email",
        "description": "Compose and send a new email. Supports JSON or multipart form-data requests.",
        "tags": [
          "Emails"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailSendRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/EmailSendRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email sent successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "$ref": "#/components/schemas/EmailItem"
                    },
                    "thread": {
                      "$ref": "#/components/schemas/EmailThread"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or email not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/generate": {
      "post": {
        "summary": "Generate email with AI",
        "description": "Generate an email subject and body from a prompt, with optional client or case context.",
        "tags": [
          "Emails"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailGenerateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated email draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailGenerateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Related client or case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/attachments/{attachmentId}": {
      "get": {
        "summary": "Download email attachment",
        "description": "Download an email attachment after validating team access.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "attachmentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Attachment binary stream.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Attachment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Storage fetch failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Storage fetch timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add email attachment to case",
        "description": "Copy an email attachment into case documents.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "attachmentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddAttachmentToCaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Attachment copied into case documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Attachment or case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Storage fetch failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Storage fetch timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/attachments/{attachmentId}/view": {
      "get": {
        "summary": "View email attachment inline",
        "description": "Stream an email attachment with Content-Disposition inline for browser viewing (e.g. PDF, images).",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "attachmentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Attachment binary stream for inline display.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Attachment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Storage fetch failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Storage fetch timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/{emailId}": {
      "get": {
        "summary": "Get email",
        "description": "Return a single email with attachment metadata. Optionally mark it read with `markRead=true`.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "emailId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "markRead",
            "schema": {
              "type": "boolean"
            },
            "description": "When true, mark inbound unread email as read before returning."
          }
        ],
        "responses": {
          "200": {
            "description": "Email detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "$ref": "#/components/schemas/EmailItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Email not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/emails/{emailId}/reply": {
      "post": {
        "summary": "Reply to email",
        "description": "Reply to an existing email in the same thread. Supports JSON or multipart form-data requests.",
        "tags": [
          "Emails"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "emailId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailReplyRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/EmailReplyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reply sent successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "$ref": "#/components/schemas/EmailItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or email not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Email not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/email-templates": {
      "get": {
        "summary": "List email templates",
        "description": "Returns email templates for the current team, including templates from the billing (parent) team when the current team is a child. Requires clients.view or email_templates.manage.",
        "tags": [
          "Email Templates"
        ],
        "responses": {
          "200": {
            "description": "List of templates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplatesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create email template",
        "description": "Create a new email template for the current team. Requires email_templates.manage and active subscription.",
        "tags": [
          "Email Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailTemplateCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateSingleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/email-templates/upload-handler": {
      "post": {
        "summary": "Vercel Blob client upload callback for email template attachments",
        "description": "JSON body from @vercel/blob/client handleUpload. Requires email_templates.manage.",
        "tags": [
          "Email Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlobClientUploadCallbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload token payload or completion acknowledgement",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BlobClientUploadTokenResponse"
                    },
                    {
                      "$ref": "#/components/schemas/BlobClientUploadCompletedAckResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Blob storage not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/email-templates/resolve": {
      "post": {
        "summary": "Resolve email template subject and HTML body",
        "description": "Merges placeholders using client/case/event context. When eventId is provided, only non-completed case events are accepted. Requires email_templates.manage or emails.send.",
        "tags": [
          "Email Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailTemplateResolveRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolved subject, htmlBody, mergeFields, requirements",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateResolveResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or inactive subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template or event not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to resolve",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/email-templates/{id}": {
      "patch": {
        "summary": "Update email template",
        "description": "Updates name, subject, HTML body, category, and/or attachments. At least one field required. Only the owning team may edit; firm templates are not editable from child teams.",
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Email template ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailTemplateUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTemplateSingleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete email template",
        "description": "Permanently removes the template for the owning team. Child teams cannot delete firm-shared templates.",
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Email template ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Template deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/docuseal/templates/{templateId}": {
      "get": {
        "summary": "Get DocuSeal template details",
        "description": "Returns DocuSeal template metadata and normalized sender-editable fields for guided prefill in MyLawyerLink.",
        "tags": [
          "DocuSeal",
          "Templates"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "templateId",
            "required": true,
            "schema": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "description": "DocuSeal template identifier."
          }
        ],
        "responses": {
          "200": {
            "description": "Template details retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealTemplateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Template not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/docuseal/credentials": {
      "get": {
        "summary": "Get DocuSeal credentials status",
        "description": "Returns whether DocuSeal is usable for the active team (including credentials inherited from a parent firm team) and per-team preferences. Not cached; configured depends on ancestor rows.",
        "tags": [
          "DocuSeal",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Status payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealCredentialsGetResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (e.g. subscription)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not used for this route",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Save DocuSeal API credentials",
        "description": "Validates the API key against DocuSeal, then stores encrypted credentials on the active team. OWNER/ADMIN with team.settings.update.",
        "tags": [
          "DocuSeal",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocuSealCredentialsPostBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved; returns effective status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealCredentialsWriteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid DocuSeal credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update DocuSeal integration preferences",
        "description": "Toggle send-via-app email and/or merge partial template field maps (template id → DocuSeal field name → merge key or literal:text). At least one of sendEmailViaApp or templateFieldMaps required. OWNER/ADMIN with team.settings.update.",
        "tags": [
          "DocuSeal",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocuSealCredentialsPatchBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealCredentialsPatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove DocuSeal credentials from the active team",
        "description": "Clears API key and related fields on this team row only; returns effective status (configured may remain true if a parent firm still has credentials). OWNER/ADMIN with team.settings.update.",
        "tags": [
          "DocuSeal",
          "Credentials"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed from this team; effective status in body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealCredentialsWriteResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/temp/{token}": {
      "get": {
        "summary": "Download temporary document",
        "description": "Short-lived download for client-only generated documents. Verifies token, expiry, and client access before streaming the blob. Returns 410 when the link has expired.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Temporary access token (UUID)"
          }
        ],
        "responses": {
          "200": {
            "description": "File binary stream (e.g. application/vnd.openxmlformats-officedocument.wordprocessingml.document or application/octet-stream)",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found (invalid or already removed token)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Document link has expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/temp/{token}/preview": {
      "delete": {
        "summary": "Discard generated document preview",
        "description": "Deletes the blob first, then the temp access row when blob removal succeeds (creator only).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preview discarded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TempPreviewDiscardResponse"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenResponse"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/temp/{token}/finalize": {
      "post": {
        "summary": "Finalize generated document preview to a case",
        "description": "Attaches the temp preview blob as a CaseDocument and deletes the temp row (blob is retained).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TempFinalizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document saved to case",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TempFinalizeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or missing case target",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Preview or case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Preview expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}": {
      "get": {
        "summary": "Get document",
        "description": "Get a single case document by ID with case and version info.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Document ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Document details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update document metadata",
        "description": "Update case document metadata (category, description, tags, isDiscovery, displayName). Requires cases.update, except IR reports (category ir_report) created by the current user may update display name, description, and tags with documents.view alone.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseDocumentUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete document",
        "description": "Delete a case document and its versions. Blob storage is cleaned up. Requires cases.delete, except the creator of an IR report may delete it with documents.view alone.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/versions": {
      "post": {
        "summary": "Upload document version",
        "description": "Upload a new version of a case document. File must be one of the allowed MIME types (documents, images, text). Maximum versions per document is enforced.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Parent document ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Version uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/DocumentVersion"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or version limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upload rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Insufficient Storage — storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/transcribe": {
      "post": {
        "summary": "Transcribe document",
        "description": "Transcribe an audio or video document and generate AI summary. For IR reports and call recordings. Document must be audio or video. Runs asynchronously when transcription starts.",
        "tags": [
          "Case Documents",
          "IR Reports"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "forceRegenerate": {
                    "type": "boolean",
                    "description": "Force re-transcription even if already transcribed"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcription completed, already existed, or newly started (returns 'Transcription started')"
          },
          "202": {
            "description": "Transcription in progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Document is not audio/video",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — permission/access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/summarize": {
      "post": {
        "summary": "Summarize document",
        "description": "Extract text and generate AI summary for a document (PDF, Word, images). For IR reports and general documents. Handler waits for processing to complete (synchronous); returns when summary is done or already existed.",
        "tags": [
          "Case Documents",
          "IR Reports"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Summary completed, already existed, or newly generated. Returns message, summary, keyPoints, actionableItems; for new summaries includes extractedTextLength and entities; for already-exists includes extractedText (truncated).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "summary": {
                      "type": "string",
                      "nullable": true
                    },
                    "extractedText": {
                      "type": "string",
                      "nullable": true
                    },
                    "extractedTextLength": {
                      "type": "number"
                    },
                    "keyPoints": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "actionableItems": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "entities": {
                      "type": "object",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found"
          },
          "409": {
            "description": "Summary already in progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Gateway Timeout — document download timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/documents/{id}/download": {
      "get": {
        "summary": "Download document",
        "description": "Securely download a case document by ID. Returns the file binary with appropriate Content-Type and Content-Disposition headers.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "File binary",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed (invalid document ID)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "Validation failed"
                    },
                    "details": {
                      "type": "object",
                      "description": "Zod formatted validation errors"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden"
          },
          "404": {
            "description": "Document not found"
          },
          "500": {
            "description": "Internal Server Error — storage/validation failures",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Gateway Timeout — request timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients": {
      "get": {
        "summary": "List clients",
        "description": "Paginated list of clients for the current team (or for an optional teamId when the caller is a member of that team with clients.view). Use query params limit and offset to request a page (e.g. limit=20, offset=0 for first page; offset=20 for second). Response includes a standardized pagination object with limit, offset, total, and hasMore. Optional search filters by name, email, or phone; search accepts letters, digits, spaces, and common email/phone characters (e.g. @ . + - ( ) ). Max 100 characters.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Optional search query (name, email, phone). Accepts letters, digits, spaces, and common email/phone characters (@ . + - ( ) ). Max 100 characters."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max number of items per page (optional; defaults to 50 when omitted)."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of items to skip (optional; use for subsequent pages, e.g. offset=limit for page 2)."
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team context override. When provided, the caller must be an active member of that team with clients.view (same as GET /api/clients/by-phone)."
          },
          {
            "in": "query",
            "name": "fields",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "search"
              ]
            },
            "description": "Optional response mode. `search` returns lightweight rows (`id`, `firstName`, `lastName`, `email`) only when a non-empty `search` query is provided."
          }
        ],
        "responses": {
          "200": {
            "description": "List of clients plus pagination metadata. Lightweight rows are returned only when both `fields=search` and a non-empty `search` query are provided; otherwise full `Client` rows are returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientsListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create client",
        "description": "Create a new client for the current team.",
        "tags": [
          "Clients"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body, validation failed (Zod), or missing required data after validation (Prisma P2011 null constraint)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "malformedJson": {
                    "summary": "Invalid JSON in request body",
                    "value": {
                      "error": "Validation failed",
                      "details": [
                        {
                          "message": "Malformed JSON"
                        }
                      ]
                    }
                  },
                  "validation": {
                    "summary": "Zod validation",
                    "value": {
                      "error": "Validation failed",
                      "details": {}
                    }
                  },
                  "nullConstraint": {
                    "summary": "Required field missing at save (P2011)",
                    "value": {
                      "error": "Cannot save because a required field must not be empty. Check required fields (for example phone numbers and labels) and try again."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Subscription or permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/by-phone": {
      "get": {
        "summary": "Find client by phone number",
        "description": "Look up a team-visible client by phone. For child teams, this also includes parent-team clients assigned to the child team.",
        "tags": [
          "Clients"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "phone",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Phone number to search for."
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team context override. When provided, the caller must be an active member of that team with clients.view permission."
          }
        ],
        "responses": {
          "200": {
            "description": "Lookup result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientByPhoneResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}": {
      "get": {
        "summary": "Get client",
        "description": "Get a single client by ID with associated cases.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team context override for visibility (same as GET /api/clients list). Caller must be an active member with clients.view on that team."
          }
        ],
        "responses": {
          "200": {
            "description": "Client details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update client",
        "description": "Update a client. Partial body allowed.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete client",
        "description": "Deletes a client when the client has no matters in the requesting team's firm scope (same rule as the client profile). In a single transaction the API then removes, in order: all payment plans for this client, all invoices for this client (child rows cascade per schema), then Case rows for this client whose teamId is not in the requesting user's firm (orphan / out-of-firm-scope only). It then re-checks that no Case remains in firm scope; if any do, the transaction aborts with 400. Otherwise it deletes the client. CLIENT_DELETE is audited inside the same transaction. Bulk child removals are audited after each deleteMany with entityId null, clientId in metadata, and count equal to the BatchPayload count from that deleteMany.",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Client deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Client has matters in firm scope — delete those first (API does not run the removal transaction)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/request-signature-from-template": {
      "post": {
        "summary": "Request DocuSeal signature from template (no case)",
        "description": "Creates a DocuSeal template submission for a client. Path id must match body clientId. Requires session team membership, active subscription, documents.upload, requireClientAccess, and DocuSeal configured for the client’s team or an ancestor. Supports optional templateFieldValues for sender-side prefill.",
        "tags": [
          "Clients",
          "DocuSeal"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client ID (must equal clientId in body)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestSignatureFromTemplateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature request created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealSignatureRequestSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Validation error or client ID mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/notes": {
      "get": {
        "summary": "List client notes",
        "description": "Return all notes for a client.",
        "tags": [
          "Client Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Client notes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "notes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClientNote"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create client note",
        "description": "Create a new note for a client.",
        "tags": [
          "Client Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Note created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "note": {
                      "$ref": "#/components/schemas/ClientNote"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/notes/{noteId}": {
      "delete": {
        "summary": "Delete client note",
        "description": "Delete a note belonging to a client.",
        "tags": [
          "Client Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "noteId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Note deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Note not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/events": {
      "get": {
        "summary": "List client case events (firm-wide timeline)",
        "description": "Aggregated `CaseEvent` rows for all matters of this client across the firm (same scope as the web client detail timeline).\nNot cached in Redis (unlike GET /api/clients/{id}): events change frequently; clients should rely on normal HTTP caching headers if added later.\n",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "teamId",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Optional team context (same as GET /api/clients/{id}). Caller must be an active member with clients.view."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset pagination (do not combine with cursor unless offset is 0)."
          },
          {
            "in": "query",
            "name": "sort",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "description": "Order by eventDate (then id)."
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Opaque cursor from the previous response nextCursor (cannot combine with offset greater than 0)."
          }
        ],
        "responses": {
          "200": {
            "description": "Events and pagination",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientCaseEventsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/documents": {
      "get": {
        "summary": "List client documents",
        "description": "Paginated list of documents for all cases belonging to this client (aggregate view). Same query params and response shape as GET /api/cases/{id}/documents.",
        "tags": [
          "Client Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client ID"
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "category",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category (general, pleading, discovery, etc.)"
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "ir_report",
                "file"
              ]
            }
          },
          {
            "in": "query",
            "name": "isDiscovery",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "in": "query",
            "name": "tag",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Search by display name or original name"
          }
        ],
        "responses": {
          "200": {
            "description": "Document list with pagination; each document includes Case id and title.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseDocument"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/cases/bulk-status": {
      "post": {
        "summary": "Bulk set case status for a client",
        "description": "Updates the workflow status on every case for this client that the caller can access,\nscoped to the same firm teams as the client detail page (parent team and child offices).\nSkips cases the user cannot update (e.g. limited assignee visibility). Task automation\nfor status change runs per updated case (same as single-case update).\n",
        "tags": [
          "Clients"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientBulkCaseStatusRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk update result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientBulkCaseStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/auto-time-backfill": {
      "post": {
        "summary": "Backfill automatic time from client activity",
        "description": "Creates missing activity-linked time entries for a client (SMS/MMS, completed calls, template-generated documents, IR reports, case events) scoped to teams that have a case for this client, firm-assigned child/office teams (ClientFirmAssignment), or the client’s owning team, all within the caller’s firm. Requires time.create, client access, and active subscription.",
        "tags": [
          "Clients",
          "Time entries"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client id"
          }
        ],
        "responses": {
          "200": {
            "description": "Backfill completed (counters include scanned and created rows).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientAutoTimeBackfillResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (no time.create or no client access)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases": {
      "get": {
        "summary": "List cases",
        "description": "Paginated list of cases for the current team. Use query params limit (1–100) and offset (≥0) to request a page. Response includes a standardized pagination object with limit, offset, total, and hasMore. Optional filters status, clientId, search.",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "active",
                "pending",
                "closed",
                "archived"
              ]
            },
            "description": "Filter by case status."
          },
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by client ID."
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Search by case title/number (alphanumeric, spaces, hyphens). Max 100 characters."
          },
          {
            "in": "query",
            "name": "fields",
            "schema": {
              "type": "string",
              "enum": [
                "search"
              ]
            },
            "description": "Optional response mode. `search` returns a lightweight case row shape optimized for global search."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Max items per page (optional; defaults to 50 when omitted)."
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Number of items to skip for pagination (optional)."
          }
        ],
        "responses": {
          "200": {
            "description": "List of cases plus pagination metadata. With `fields=search` and a non-empty `search`, `cases` uses a lightweight search row shape.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CasesListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create case",
        "description": "Create a new case. Requires clientId. Title is server-generated in the format LastName, FirstName - {caseNumber or —} - count (uses provided caseNumber verbatim and falls back to an em dash when caseNumber is missing).",
        "tags": [
          "Cases"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Case created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case": {
                      "$ref": "#/components/schemas/Case"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Client not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}": {
      "get": {
        "summary": "Get case",
        "description": "Get a single case by ID with client and events.",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Case details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case": {
                      "$ref": "#/components/schemas/Case"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update case",
        "description": "Update a case. Partial body allowed.",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Case updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case": {
                      "$ref": "#/components/schemas/Case"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete case",
        "description": "Delete a case and associated events.",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Case deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Delete blocked by a referencing record (foreign key)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/unbilled-time-entries": {
      "get": {
        "summary": "List unbilled time entries",
        "description": "Return unbilled time entries for a case, useful when creating invoices.",
        "tags": [
          "Invoices"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unbilled time entry page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "timeEntries": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TimeEntry"
                          }
                        },
                        "pagination": {
                          "$ref": "#/components/schemas/PaginationMetadata"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/retainer-summary": {
      "get": {
        "summary": "Case retainer summary",
        "description": "Deposits total, billable fees to date, balance, and recent deposit rows (practice management; not trust accounting).",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Retainer summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseRetainerSummarySuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/retainer-deposits": {
      "post": {
        "summary": "Record retainer deposit",
        "description": "Append a deposit row for the case (practice management; not trust accounting).",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "description": "When set, duplicate requests return the original deposit and summary instead of creating a second row."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RetainerDepositCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deposit created; returns updated summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetainerDepositCreateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON or validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/request-signature-from-template": {
      "post": {
        "summary": "Request DocuSeal signature from template",
        "description": "Creates a DocuSeal submission from a template for the case client. Requires session team membership, active subscription, documents.upload, case access (including firm hierarchy), and DocuSeal configured for the case’s team or an ancestor. Body matches requestSignatureFromTemplateBodySchema and supports optional templateFieldValues for sender-side prefill.",
        "tags": [
          "Cases",
          "DocuSeal"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestSignatureFromTemplateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature request created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealSignatureRequestSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Validation error, client mismatch, or DocuSeal not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (subscription or permissions)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found for current access context, or DocuSeal template not found (createSignatureRequestWithValidationHandling / DocuSealTemplateDetailsError)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests when rate limiting applies",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error (including DocuSeal not configured for team when returned as 500)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "DocuSeal upstream failure or invalid template payload (createSignatureRequestWithValidationHandling / DocuSealTemplateDetailsError upstream)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/notes": {
      "get": {
        "summary": "List case notes",
        "description": "Return all notes for a case.",
        "tags": [
          "Case Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Case notes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "notes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseNote"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create case note",
        "description": "Create a new note for a case.",
        "tags": [
          "Case Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Note created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "note": {
                      "$ref": "#/components/schemas/CaseNote"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/notes/{noteId}": {
      "delete": {
        "summary": "Delete case note",
        "description": "Delete a note belonging to a case.",
        "tags": [
          "Case Notes"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "path",
            "name": "noteId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Note deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Note not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/events": {
      "post": {
        "summary": "Create case event",
        "description": "Create a new event for a case.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseEventInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "event": {
                      "$ref": "#/components/schemas/CaseEvent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/email-attachment": {
      "post": {
        "summary": "Add email attachment to case and link thread",
        "description": "Copies an email attachment into case documents, then links the email thread to the case. If linking fails, removes the copied document server-side (no cases.delete required).",
        "tags": [
          "Cases"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddEmailAttachmentAndLinkThreadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document created and thread linked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddEmailAttachmentAndLinkThreadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/documents": {
      "get": {
        "summary": "List case documents",
        "description": "Paginated case documents list. Query uses page (default 1) and limit (default 50) for backward compatibility, and the response includes the standardized pagination object with limit, offset, total, and hasMore. Optional filters category, type (all|ir_report|file), isDiscovery, tag, and search query (q). HTTP 429 for this operation is returned only when server rate limiting is enforced (isRateLimitingEnforced; not in default vite dev unless RATE_LIMIT_ENABLED=true), using GET_RATE_LIMIT.MAX_REQUESTS per GET_RATE_LIMIT.WINDOW_SECONDS per authenticated user.",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          },
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "category",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category (general, pleading, discovery, etc.)"
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "ir_report",
                "file"
              ]
            }
          },
          {
            "in": "query",
            "name": "isDiscovery",
            "schema": {
              "type": "boolean"
            },
            "description": "Filter by discovery flag (query value true/false)"
          },
          {
            "in": "query",
            "name": "tag",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "schema": {
              "type": "string"
            },
            "description": "Search by display name or original name"
          }
        ],
        "responses": {
          "200": {
            "description": "Document list with standardized pagination metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseDocument"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/PaginationMetadata"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — only when isRateLimitingEnforced is true (not default vite dev; set RATE_LIMIT_ENABLED=true to enforce locally). Uses GET_RATE_LIMIT.MAX_REQUESTS per GET_RATE_LIMIT.WINDOW_SECONDS per user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Upload case document",
        "description": "Upload a document to a case. Accepts documents, images, audio, video, and text files. HTTP 429 is returned only when server rate limiting is enforced (isRateLimitingEnforced; not in default vite dev unless RATE_LIMIT_ENABLED=true), using RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per authenticated user (storage-constants).",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "general",
                      "pleading",
                      "discovery",
                      "evidence",
                      "contract",
                      "correspondence",
                      "filing",
                      "other",
                      "ir_report"
                    ]
                  },
                  "description": {
                    "type": "string"
                  },
                  "displayName": {
                    "type": "string"
                  },
                  "isDiscovery": {
                    "type": "boolean"
                  },
                  "tags": {
                    "type": "string",
                    "description": "Comma-separated tags"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upload rate limit exceeded — only when isRateLimitingEnforced is true (not default vite dev; set RATE_LIMIT_ENABLED=true to enforce locally). Uses RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Insufficient Storage — storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/documents/prepare-upload": {
      "post": {
        "summary": "Prepare large case document upload",
        "description": "Creates a placeholder case document and returns pathname and documentId for client-side upload to Vercel Blob (files larger than the serverless body limit).\nHTTP 429 is returned only when rate limiting is enforced (isRateLimitingEnforced; not in default vite dev unless RATE_LIMIT_ENABLED=true), using RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per team.\n",
        "tags": [
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrepareUploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Placeholder created; upload blob using pathname then complete client flow",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrepareUploadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized or not a team member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (e.g. documents.upload)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upload rate limit exceeded — only when isRateLimitingEnforced is true. Uses RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per team.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Insufficient Storage — storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/documents/ir-report": {
      "post": {
        "summary": "Create IR report",
        "description": "Create an Interaction Report (IR Report) as a case document. Accepts text content, audio files (transcribed automatically), documents, or images (summarized in background). Requires documents.upload permission. At least one of text or file is required. HTTP 429 is returned only when rate limiting is enforced (isRateLimitingEnforced === true; not in default vite dev unless RATE_LIMIT_ENABLED=true), using RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per authenticated user.",
        "tags": [
          "IR Reports",
          "Case Documents"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "irReportType"
                ],
                "anyOf": [
                  {
                    "type": "object",
                    "required": [
                      "text"
                    ]
                  },
                  {
                    "type": "object",
                    "required": [
                      "file"
                    ]
                  }
                ],
                "properties": {
                  "irReportType": {
                    "type": "string",
                    "enum": [
                      "client_phone_call",
                      "prosecutor_phone_call",
                      "other_phone_call",
                      "closing_call",
                      "intro_call",
                      "info_only",
                      "post_hearing",
                      "case_ready_to_close",
                      "discovery_check"
                    ]
                  },
                  "text": {
                    "type": "string",
                    "description": "Text content (for text-only IR report)"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Audio (m4a, webm, mp3), document (PDF, Word), or image (JPEG, PNG, etc.)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "IR report created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/CaseDocument"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — insufficient permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Case not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Upload rate limit exceeded — only when isRateLimitingEnforced is true (not default vite dev; set RATE_LIMIT_ENABLED=true to enforce locally). Uses RATE_LIMIT.MAX_UPLOADS per RATE_LIMIT.WINDOW_SECONDS per user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "507": {
            "description": "Insufficient Storage — storage quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/cases/{id}/documents/{documentId}/request-signature": {
      "post": {
        "summary": "Request DocuSeal signature for a case document",
        "description": "Sends a case document PDF to DocuSeal and returns a signing URL. Requires session team membership, active subscription, documents.upload, requireCaseAccess for the case, DocuSeal configured for the case’s owning team or an ancestor (createSignatureRequest / isDocuSealConfigured). Returns 404 with Resource not found when the case is not accessible or the document is missing for the case team.",
        "tags": [
          "Cases",
          "Case Documents",
          "DocuSeal"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case ID"
          },
          {
            "in": "path",
            "name": "documentId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Case document ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestSignatureBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature request created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocuSealSignatureRequestSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Validation error, client mismatch, or DocuSeal not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (subscription or permissions)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (case not accessible or document not on case)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls": {
      "get": {
        "summary": "List calls",
        "description": "Paginated call history for the current team with optional search across phone numbers, client names, and recording transcription fields.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Call history page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallsListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/voicemails": {
      "get": {
        "summary": "List voicemails",
        "description": "Paginated voicemail list for the current team.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Voicemail page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoicemailsListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/recordings": {
      "get": {
        "summary": "List call recordings",
        "description": "Return team call recordings filtered by `clientId` or `caseId`.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "clientId",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "caseId",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching call recordings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallRecordingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing required filters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/recordings/{id}": {
      "put": {
        "summary": "Update call recording",
        "description": "Update editable recording metadata such as display name.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRecordingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated recording.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recording": {
                      "$ref": "#/components/schemas/CallRecordingSummary"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Recording not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/recordings/{id}/transcript": {
      "get": {
        "summary": "Get call recording transcript payload",
        "description": "Returns transcription text and transcriptionData for a team-owned recording. Used when expanding transcription in the UI after the initial page load.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transcript fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallRecordingTranscriptResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid recording id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Recording not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/recordings/{id}/transcribe": {
      "post": {
        "summary": "Start call transcription",
        "description": "Start transcription and AI summary generation for a call recording.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartTranscriptionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcription started or existing transcription returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "recording": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "transcriptionStatus": {
                          "type": "string"
                        },
                        "transcription": {
                          "type": "string",
                          "nullable": true
                        },
                        "summary": {
                          "type": "string",
                          "nullable": true
                        },
                        "keyPoints": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "nullable": true
                        },
                        "actionableItems": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Transcription already in progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Recording not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calls/{id}/recording/download": {
      "get": {
        "summary": "Download call recording",
        "description": "Download a call recording binary after verifying team access and recording ownership.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string"
                }
              ]
            },
            "description": "Internal call ID or Twilio Call SID."
          }
        ],
        "responses": {
          "200": {
            "description": "Recording binary stream.",
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/mp3": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/x-wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid call id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Call or recording not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "504": {
            "description": "Recording download timeout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calendar/generate-token": {
      "post": {
        "summary": "Generate calendar feed token",
        "description": "Generate a token for the iCalendar feed URL. Returns plain token once (store securely).",
        "tags": [
          "Calendar"
        ],
        "responses": {
          "200": {
            "description": "Token generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Revoke calendar feed token",
        "description": "Revoke the user's calendar feed token.",
        "tags": [
          "Calendar"
        ],
        "responses": {
          "200": {
            "description": "Token revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calendar/feeds": {
      "get": {
        "summary": "List calendar feeds",
        "description": "List all named calendar feeds for the authenticated team (Events & Calendar). Use feed token in GET /api/calendar/feed/{userId}/{token} to subscribe.",
        "tags": [
          "Calendar Feeds"
        ],
        "responses": {
          "200": {
            "description": "List of feeds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedsListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create calendar feed",
        "description": "Create a new named calendar feed (name + event type filter). Returns the feed including token for the subscribe URL.",
        "tags": [
          "Calendar Feeds"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalendarFeedInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Feed created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calendar/feeds/{id}": {
      "put": {
        "summary": "Update calendar feed",
        "description": "Update a named calendar feed (partial; name and/or eventTypes).",
        "tags": [
          "Calendar Feeds"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalendarFeedUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feed updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Feed not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete calendar feed",
        "description": "Delete a named calendar feed. Feed owner or team admin can delete. Invalidates the feed URL.",
        "tags": [
          "Calendar Feeds"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feed deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Feed not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/calendar/feeds/{id}/token": {
      "get": {
        "summary": "Reveal feed token and owner userId",
        "description": "Returns the feed token and owner userId for building the subscribe URL (e.g. Copy URL). Team-scoped; requires events.view and active subscription. List endpoint does not include token.",
        "tags": [
          "Calendar Feeds"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Calendar feed id"
          }
        ],
        "responses": {
          "200": {
            "description": "Token and owner userId for feed URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedTokenResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Feed not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/signup": {
      "post": {
        "summary": "Sign up",
        "description": "Create a new user account. Requires username, email, password; optional timezone.\nSignup rate limiting applies when limits are enforced (not during vite dev unless RATE_LIMIT_ENABLED=true).\nSet SIGNUP_RATE_LIMIT_ENABLED=false to disable signup limits in production (e.g. bulk signups).\n",
        "tags": [
          "Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "email",
                  "password"
                ],
                "properties": {
                  "username": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 31
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 6,
                    "maxLength": 255
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created; returns success and user",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or username/email taken",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many signup attempts from this IP (rate limit exceeded)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "details": {
                      "type": "object",
                      "properties": {
                        "retryAfter": {
                          "type": "number",
                          "description": "Seconds until retry allowed"
                        }
                      }
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/signout": {
      "post": {
        "summary": "Sign out",
        "description": "Invalidate session and clear session cookie.",
        "tags": [
          "Auth"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/signin": {
      "post": {
        "summary": "Sign in",
        "description": "Authenticate by identifier (email or username) and password. Sets session cookie on success.",
        "tags": [
          "Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignInRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignInResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Account lockout active due to repeated failed attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TooManyRequestsResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/session": {
      "get": {
        "summary": "Get current session",
        "description": "Returns session data (user, team, teams) if authenticated via session cookie.",
        "tags": [
          "Auth"
        ],
        "responses": {
          "200": {
            "description": "Session data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "user": {
                          "$ref": "#/components/schemas/User"
                        },
                        "team": {
                          "$ref": "#/components/schemas/Team"
                        },
                        "teams": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "id",
                              "name",
                              "slug",
                              "emailSlug",
                              "role",
                              "isCurrent",
                              "callDoNotDisturbUntil",
                              "callDoNotDisturbIndefinite"
                            ],
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "slug": {
                                "type": "string"
                              },
                              "emailSlug": {
                                "type": "string",
                                "nullable": true
                              },
                              "role": {
                                "type": "string"
                              },
                              "isCurrent": {
                                "type": "boolean"
                              },
                              "callDoNotDisturbUntil": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true,
                                "description": "When set and in the future, browser ring is suppressed for that team (timed)"
                              },
                              "callDoNotDisturbIndefinite": {
                                "type": "boolean",
                                "description": "When true, browser ring stays off until user clears DnD"
                              }
                            }
                          }
                        },
                        "canAccessFirmView": {
                          "type": "boolean"
                        },
                        "isParentTeam": {
                          "type": "boolean"
                        },
                        "twilioConfiguredOnAnyTeam": {
                          "type": "boolean",
                          "description": "True if Twilio is configured for at least one team the user belongs to"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No session or invalid session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/reset-password": {
      "post": {
        "summary": "Reset password with token",
        "description": "Set a new password using a valid reset token from the forgot-password email link.",
        "tags": [
          "Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "password"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Reset token from email link"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 6,
                    "maxLength": 255,
                    "description": "New password"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password reset successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed or invalid/expired token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/forgot-password": {
      "post": {
        "summary": "Request password reset",
        "description": "Submit email to receive a password reset link. Always returns 200 to avoid email enumeration (same response for valid email, unknown email, or misconfiguration).",
        "tags": [
          "Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForgotPasswordRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request accepted (link sent if account exists; same body for all outcomes to avoid enumeration)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error (response body still 200 for enumeration safety when applicable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/change-password": {
      "post": {
        "summary": "Change user password",
        "description": "Change the authenticated user's password. Requires current password and new password. Session cookie required.",
        "tags": [
          "Auth"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "currentPassword",
                  "newPassword"
                ],
                "properties": {
                  "currentPassword": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Current password"
                  },
                  "newPassword": {
                    "type": "string",
                    "minLength": 6,
                    "maxLength": 255,
                    "description": "New password"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password changed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthenticated or current password incorrect",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Authorization failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/assistant/chat": {
      "post": {
        "summary": "Send message to Redwell assistant",
        "description": "Send a natural language message to the Redwell AI assistant. Optional attachments (JPEG/PNG/GIF/WebP images or PDF, standard base64, up to 4 files, ~10MB decoded each)—images are sent to the vision model; PDFs are text-extracted server-side. Declared MIME must match file content (magic-byte sniff). The `message` field is always required (use an empty string when sending only attachments). Returns the assistant's response. Requires authentication and team membership. The assistant can query events, tasks (events with type task), clients, cases (including case and client custom field values), time entries, invoices, payment plans, calls, SMS, team-scoped email threads, and case documents including IR reports; load custom field definitions; run practice-wide source reports over calls, SMS, email, IR reports, and calendar (same data model as AI knowledge digests, answered in chat by default); list, search, read, create, update, and delete knowledge wiki pages (within permissions); create and edit events; and manage task automations. With `team.firm_view` on the billing (parent) team, the assistant can also list firm-wide clients, email threads, and audit logs (same scope as `/api/firm/*`). Each data tool enforces the same permissions as the corresponding app APIs. For users who are Owner or Admin on the current team, the assistant can also explain team member roles and permissions. Some hosts, proxies, or API gateways enforce a smaller HTTP request-body limit than the per-file cap; clients should confirm their platform limit, split or reduce payloads, or upload large assets via a dedicated upload or storage service instead of embedding huge base64 in JSON.",
        "tags": [
          "Assistant"
        ],
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssistantChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Depends on request body `stream`. When `stream` is false or omitted, the body is `application/json` (`AssistantChatResponse`). When `stream` is true, the body is `application/x-ndjson`: one JSON object per line. NDJSON lines use `AssistantChatNdjsonEvent` (discriminated by `type` status, done, or error); see those schemas for `label`, `answer`, `threadId`, and `error` fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssistantChatResponse"
                }
              },
              "application/x-ndjson": {
                "schema": {
                  "$ref": "#/components/schemas/AssistantChatNdjsonEvent"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body or attachment payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "PDF text extraction upstream failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "REDWELL_MCP_TOOLS_ONLY=true but no hosted MCP tools available (server configuration)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/api-keys": {
      "get": {
        "summary": "List API keys",
        "description": "List API keys for the current team. Requires session auth (not API key).",
        "tags": [
          "API keys"
        ],
        "responses": {
          "200": {
            "description": "List of API keys (without raw secret)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized or API key auth not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create API key",
        "description": "Create a new API key. Raw key returned once. Requires session auth.",
        "tags": [
          "API keys"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "scope"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "read",
                      "read_write"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key created (raw key in data.key, returned once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "API key ID"
                        },
                        "name": {
                          "type": "string",
                          "description": "Key name"
                        },
                        "keyPrefix": {
                          "type": "string",
                          "description": "Key prefix for display"
                        },
                        "scope": {
                          "type": "string",
                          "description": "read or read_write"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the key was created"
                        },
                        "key": {
                          "type": "string",
                          "description": "Raw key (only returned on create)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden or subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/practice": {
      "get": {
        "summary": "Get practice settings",
        "description": "Returns practice settings for the current team.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Practice settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "practiceName": {
                      "type": "string"
                    },
                    "practicePhone": {
                      "type": "string"
                    },
                    "practiceEmail": {
                      "type": "string"
                    },
                    "practiceAddress1": {
                      "type": "string"
                    },
                    "practiceAddress2": {
                      "type": "string"
                    },
                    "practiceCustomFields": {
                      "type": "array"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized or not a team member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update practice settings",
        "description": "Update practice settings for the current team.",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "practiceName": {
                    "type": "string"
                  },
                  "practicePhone": {
                    "type": "string"
                  },
                  "practiceEmail": {
                    "type": "string"
                  },
                  "practiceAddress1": {
                    "type": "string"
                  },
                  "practiceAddress2": {
                    "type": "string"
                  },
                  "practiceCustomFields": {
                    "type": "array"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden - permission denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/owned-teams": {
      "get": {
        "summary": "List teams owned by current user",
        "description": "Returns teams where the current user has role OWNER.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "List of owned teams",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "teams": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Team"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/invite": {
      "post": {
        "summary": "Invite user to teams",
        "description": "Invite a user by email to one or more teams (owner-only). Body email, role, teamIds.",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role",
                  "teamIds"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "OWNER",
                      "ADMIN",
                      "MEMBER"
                    ]
                  },
                  "teamIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation(s) created, or user auto-added if they already had an account",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/InviteToTeamsInviteResponse"
                    },
                    {
                      "$ref": "#/components/schemas/InviteToTeamsAutoAddResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (not owner or no seats)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/case-fields": {
      "put": {
        "summary": "Update case field configuration",
        "description": "Update case field configuration for the current team.",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fieldConfig": {
                    "type": "object"
                  },
                  "customFields": {
                    "type": "array"
                  },
                  "customClientFields": {
                    "type": "array"
                  },
                  "customEventFields": {
                    "type": "array"
                  },
                  "eventTitlePresets": {
                    "type": "array",
                    "description": "Optional firm-level title suggestions grouped by event type. Child teams inherit from firm.",
                    "items": {
                      "type": "object",
                      "required": [
                        "eventType",
                        "titles"
                      ],
                      "properties": {
                        "eventType": {
                          "type": "string",
                          "enum": [
                            "court_date",
                            "deadline",
                            "meeting",
                            "communication",
                            "filing",
                            "document",
                            "billing",
                            "task",
                            "drive_time",
                            "trial",
                            "other"
                          ]
                        },
                        "titles": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 200
                          }
                        }
                      }
                    }
                  },
                  "firmMergeFieldDefinitions": {
                    "type": "array",
                    "description": "Optional; firm-defined merge keys (e.g. attorney_name). Only parent teams may PUT.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "caseStatusDefinitions": {
                    "type": "array",
                    "description": "Optional; firm-only ordered list of { value, label }. Omit to leave unchanged. When removing a value still used by cases, include caseStatusRemap or the API returns 400 with details.",
                    "items": {
                      "type": "object",
                      "required": [
                        "value",
                        "label"
                      ],
                      "properties": {
                        "value": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "label": {
                          "type": "string",
                          "maxLength": 120
                        }
                      }
                    }
                  },
                  "caseStatusRemap": {
                    "type": "object",
                    "description": "Optional map from removed status value to replacement (must appear in caseStatusDefinitions). Required when save would orphan cases.",
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 64
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseFieldsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Team not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message"
          },
          "code": {
            "type": "string",
            "description": "Optional stable machine-readable code for clients (e.g. VOICE_PUSH_CREDENTIAL_MISSING)"
          },
          "details": {
            "type": "object",
            "description": "Optional validation or error details"
          }
        },
        "required": [
          "error"
        ]
      },
      "ClientAutoTimeBackfillData": {
        "type": "object",
        "description": "Activity scan counts from client auto-time backfill",
        "required": [
          "created",
          "consideredSms",
          "consideredCalls",
          "consideredDocuments",
          "consideredIrReports",
          "consideredCaseEvents",
          "skippedSmsNoUser",
          "skippedCallNoUser",
          "skippedDocumentNoUser",
          "skippedIrReportNoUser",
          "skippedCaseEventNoUser",
          "skippedCaseEventIncompleteTask",
          "skippedNoResolvedCase"
        ],
        "properties": {
          "created": {
            "type": "integer",
            "minimum": 0
          },
          "consideredSms": {
            "type": "integer",
            "minimum": 0
          },
          "consideredCalls": {
            "type": "integer",
            "minimum": 0
          },
          "consideredDocuments": {
            "type": "integer",
            "minimum": 0
          },
          "consideredIrReports": {
            "type": "integer",
            "minimum": 0
          },
          "consideredCaseEvents": {
            "type": "integer",
            "minimum": 0
          },
          "skippedSmsNoUser": {
            "type": "integer",
            "minimum": 0
          },
          "skippedCallNoUser": {
            "type": "integer",
            "minimum": 0
          },
          "skippedDocumentNoUser": {
            "type": "integer",
            "minimum": 0
          },
          "skippedIrReportNoUser": {
            "type": "integer",
            "minimum": 0
          },
          "skippedCaseEventNoUser": {
            "type": "integer",
            "minimum": 0
          },
          "skippedCaseEventIncompleteTask": {
            "type": "integer",
            "minimum": 0
          },
          "skippedNoResolvedCase": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ClientAutoTimeBackfillResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ClientAutoTimeBackfillData"
          }
        }
      },
      "UserCallDoNotDisturbUpdate": {
        "type": "object",
        "required": [
          "teamIds",
          "durationMinutes"
        ],
        "properties": {
          "teamIds": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Unique team IDs (duplicates are ignored server-side)"
          },
          "durationMinutes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440,
            "description": "0 with indefinite false clears DnD; positive sets timed end; use 0 with indefinite true for permanent DnD until cleared"
          },
          "indefinite": {
            "type": "boolean",
            "description": "When true, DnD stays on until cleared (must not send positive durationMinutes)"
          }
        }
      },
      "UserCallDoNotDisturbResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "updatedCount",
              "until",
              "indefinite"
            ],
            "properties": {
              "updatedCount": {
                "type": "integer",
                "minimum": 1
              },
              "until": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "End of DnD window when timed; null when cleared or indefinite"
              },
              "indefinite": {
                "type": "boolean",
                "description": "True when permanent DnD was applied for the update"
              }
            }
          }
        }
      },
      "ForbiddenResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Forbidden/error message"
          },
          "retryAfter": {
            "type": "number",
            "description": "Seconds until retry is allowed (when applicable)"
          },
          "details": {
            "type": "object",
            "description": "Optional validation or error details"
          }
        },
        "required": [
          "error"
        ]
      },
      "TooManyRequestsResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Rate-limit error message"
          },
          "retryAfter": {
            "type": "number",
            "description": "Seconds until retry is allowed"
          }
        },
        "required": [
          "error",
          "retryAfter"
        ]
      },
      "SmsTemplateReplacementOption": {
        "type": "object",
        "description": "A candidate SMS template on the same team (delete preview or error context).",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "description": "Present when the template has a category"
          }
        }
      },
      "SmsTemplateDeletePreview": {
        "type": "object",
        "description": "GET /api/sms-templates/{id}/delete-preview success body; uses the same referencedEventCount semantics as SmsTemplateDeleteErrorResponse.",
        "required": [
          "referencedEventCount",
          "replacementTemplates"
        ],
        "properties": {
          "referencedEventCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Distinct case events that reference this template in any SMS slot"
          },
          "replacementTemplates": {
            "type": "array",
            "description": "Other templates on the template owner team that may be chosen as replacementTemplateId on DELETE",
            "items": {
              "$ref": "#/components/schemas/SmsTemplateReplacementOption"
            }
          }
        }
      },
      "SmsTemplateDeleteRequest": {
        "type": "object",
        "description": "Optional JSON body for DELETE /api/sms-templates/{id}. Matches deleteBodySchema (replacementTemplateId optional; null treated like omitted).",
        "properties": {
          "replacementTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Another SMS template on the same team; required in practice when case events still reference the template being deleted (server re-checks inside the delete transaction)."
          }
        }
      },
      "SmsTemplateDeleteResponse": {
        "type": "object",
        "required": [
          "success"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Template was deleted (and event SMS links repointed when applicable)"
          }
        }
      },
      "SmsTemplateDeleteErrorResponse": {
        "type": "object",
        "description": "400 validation or replacement-required responses for DELETE /api/sms-templates/{id}",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Present when JSON body fails Zod validation (mirrors other API validation errors)"
          },
          "referencedEventCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Present when events reference the template and replacementTemplateId was missing or invalid"
          },
          "replacementTemplates": {
            "type": "array",
            "description": "Optional; same shape as delete-preview when the API includes replacement choices with an error",
            "items": {
              "$ref": "#/components/schemas/SmsTemplateReplacementOption"
            }
          },
          "staleReferenceCount": {
            "type": "integer",
            "minimum": 1,
            "description": "Present on 409 when references to the template remained after repointing (concurrent changes); client should refresh preview and retry"
          }
        }
      },
      "TaskAutomationFieldRemovalWarning": {
        "type": "object",
        "description": "A custom field was removed from firm settings while task automation rules still reference it.",
        "required": [
          "scope",
          "fieldName",
          "ruleTitles"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "case",
              "client",
              "event"
            ],
            "description": "Whether the removed definition was a case, client, or event custom field."
          },
          "fieldName": {
            "type": "string",
            "description": "Custom field name (key) that was removed in this update"
          },
          "ruleTitles": {
            "type": "array",
            "description": "Display titles of matching automation rules (rule name, or task title fallback).",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "EventTitlePresetTitlesForType": {
        "type": "array",
        "description": "Non-empty list of preset titles for one event type (matches server Zod; max 30 per type).",
        "minItems": 1,
        "maxItems": 30,
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        }
      },
      "EventTitlePresetsByTypeMap": {
        "type": "object",
        "description": "Title suggestions keyed by event type; only types with presets appear. Matches GET /api/cases/{id}/event-defaults and server normalization.",
        "additionalProperties": false,
        "properties": {
          "court_date": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "deadline": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "meeting": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "communication": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "filing": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "document": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "billing": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "task": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "drive_time": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "trial": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          },
          "other": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          }
        }
      },
      "EventTitlePresetRowResponse": {
        "type": "object",
        "description": "One event type and its preset titles (PUT /api/account/case-fields echo row; matches eventTitlePresetTypeSchema).",
        "required": [
          "eventType",
          "titles"
        ],
        "additionalProperties": false,
        "properties": {
          "eventType": {
            "type": "string",
            "enum": [
              "court_date",
              "deadline",
              "meeting",
              "communication",
              "filing",
              "document",
              "billing",
              "task",
              "drive_time",
              "trial",
              "other"
            ]
          },
          "titles": {
            "$ref": "#/components/schemas/EventTitlePresetTitlesForType"
          }
        }
      },
      "CaseFieldsResponse": {
        "type": "object",
        "description": "PUT /api/account/case-fields success body.",
        "required": [
          "success"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "caseStatuses": {
            "type": "array",
            "description": "Omitted unless caseStatusDefinitions was in the request; echoes saved value/label pairs for reference data.",
            "items": {
              "type": "object",
              "required": [
                "value",
                "label"
              ],
              "properties": {
                "value": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          },
          "eventTitlePresets": {
            "type": "array",
            "description": "Omitted unless eventTitlePresets was in the request; echoes saved event title suggestions by event type (max one row per type, max 11 rows).",
            "maxItems": 11,
            "items": {
              "$ref": "#/components/schemas/EventTitlePresetRowResponse"
            }
          },
          "caseStatusMigration": {
            "type": "object",
            "description": "Omitted when no case rows or automation rules were updated due to status remap.",
            "properties": {
              "remappedCaseCount": {
                "type": "integer",
                "minimum": 0
              },
              "updatedAutomationRuleCount": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "taskAutomationWarnings": {
            "type": "array",
            "description": "Omitted when empty. Present when removed custom fields are still referenced by task automation rules (same snapshot as the successful update).",
            "items": {
              "$ref": "#/components/schemas/TaskAutomationFieldRemovalWarning"
            }
          }
        }
      },
      "CaseEventDefaultsResponse": {
        "type": "object",
        "required": [
          "defaultLocation",
          "eventTitlePresets"
        ],
        "description": "GET /api/cases/{id}/event-defaults success payload used by event creation forms.",
        "properties": {
          "defaultLocation": {
            "type": "string",
            "nullable": true,
            "description": "Suggested location based on case defaults/previous events; null when none is available."
          },
          "eventTitlePresets": {
            "$ref": "#/components/schemas/EventTitlePresetsByTypeMap"
          }
        }
      },
      "AssistantChatRequest": {
        "anyOf": [
          {
            "type": "object",
            "description": "Text-only or non-empty message (attachments optional).",
            "required": [
              "message"
            ],
            "properties": {
              "message": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2000,
                "description": "Non-empty user message"
              },
              "threadId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Optional thread ID to continue a conversation"
              },
              "attachments": {
                "type": "array",
                "maxItems": 4,
                "description": "Optional images or PDFs (standard base64; ~10MB decoded per file max; total decoded size capped)",
                "items": {
                  "type": "object",
                  "required": [
                    "mimeType",
                    "dataBase64"
                  ],
                  "properties": {
                    "mimeType": {
                      "type": "string",
                      "enum": [
                        "image/jpeg",
                        "image/png",
                        "image/gif",
                        "image/webp",
                        "application/pdf"
                      ]
                    },
                    "dataBase64": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 13981016,
                      "description": "Standard base64 (max ~10MB decoded; maxLength matches server Zod)"
                    },
                    "filename": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 255,
                      "description": "Optional; no path separators or control characters per server validation"
                    }
                  }
                }
              },
              "context": {
                "type": "object",
                "description": "Optional page context (case/client) for the assistant to use",
                "properties": {
                  "caseId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "clientId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                }
              },
              "stream": {
                "type": "boolean",
                "description": "When true, returns application/x-ndjson (one JSON object per line) with type \"status\" (and label), then type \"done\" (answer, threadId) on success, or type \"error\" (error) on failure or cancellation; omit or false for application/json { answer, threadId }."
              }
            }
          },
          {
            "type": "object",
            "description": "Attachments with optional empty message (at least one file required).",
            "required": [
              "message",
              "attachments"
            ],
            "properties": {
              "message": {
                "type": "string",
                "maxLength": 2000,
                "description": "May be empty when attachments has at least one item"
              },
              "threadId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Optional thread ID to continue a conversation"
              },
              "attachments": {
                "type": "array",
                "minItems": 1,
                "maxItems": 4,
                "description": "At least one image or PDF for Redwell (standard base64; ~10MB decoded per file max; total decoded size capped)",
                "items": {
                  "type": "object",
                  "required": [
                    "mimeType",
                    "dataBase64"
                  ],
                  "properties": {
                    "mimeType": {
                      "type": "string",
                      "enum": [
                        "image/jpeg",
                        "image/png",
                        "image/gif",
                        "image/webp",
                        "application/pdf"
                      ]
                    },
                    "dataBase64": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 13981016,
                      "description": "Standard base64 (max ~10MB decoded; maxLength matches server Zod)"
                    },
                    "filename": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 255,
                      "description": "Optional; no path separators or control characters per server validation"
                    }
                  }
                }
              },
              "context": {
                "type": "object",
                "description": "Optional page context (case/client) for the assistant to use",
                "properties": {
                  "caseId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "clientId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                }
              },
              "stream": {
                "type": "boolean",
                "description": "When true, returns application/x-ndjson with type \"status\", then \"done\" or \"error\" lines (same wire protocol as the text-only stream variant)."
              }
            }
          }
        ]
      },
      "AssistantChatResponse": {
        "type": "object",
        "required": [
          "answer",
          "threadId"
        ],
        "properties": {
          "answer": {
            "type": "string",
            "description": "Assistant reply text"
          },
          "threadId": {
            "type": "string",
            "format": "uuid",
            "description": "Thread ID for follow-up messages"
          },
          "mcpToolIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "When Redwell uses hosted MCP tools only, sorted AI SDK tool names (typically prefixed mcp_) registered for this turn"
          }
        }
      },
      "AssistantChatNdjsonStatusEvent": {
        "type": "object",
        "required": [
          "type",
          "label"
        ],
        "description": "NDJSON line emitted while tools run or the model is thinking (stream:true only).",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "status"
            ]
          },
          "label": {
            "type": "string",
            "description": "User-visible status line"
          }
        }
      },
      "AssistantChatNdjsonDoneEvent": {
        "type": "object",
        "required": [
          "type",
          "answer",
          "threadId"
        ],
        "description": "Final NDJSON line when the assistant reply is ready (stream:true only).",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "done"
            ]
          },
          "answer": {
            "type": "string",
            "description": "Assistant reply text"
          },
          "threadId": {
            "type": "string",
            "format": "uuid",
            "description": "Thread ID for follow-up messages"
          },
          "mcpToolIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "When Redwell uses hosted MCP tools only, sorted tool ids for this turn (optional)"
          }
        }
      },
      "AssistantChatNdjsonErrorEvent": {
        "type": "object",
        "required": [
          "type",
          "error"
        ],
        "description": "NDJSON line when the streamed turn fails (stream:true only).",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "error": {
            "type": "string",
            "description": "User-safe error message"
          }
        }
      },
      "AssistantChatNdjsonEvent": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/AssistantChatNdjsonStatusEvent"
          },
          {
            "$ref": "#/components/schemas/AssistantChatNdjsonDoneEvent"
          },
          {
            "$ref": "#/components/schemas/AssistantChatNdjsonErrorEvent"
          }
        ],
        "description": "Single NDJSON line (JSON object) when POST body includes stream:true. The response body is many such lines, each terminated by a newline; clients should read line-by-line. Typical sequence includes one or more status lines, then either done or error."
      },
      "CronPaymentRemindersResponse": {
        "type": "object",
        "description": "Result of the payment plan reminders cron run.",
        "required": [
          "success",
          "timestamp",
          "results"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "ISO timestamp when the run completed"
          },
          "reminderDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "ISO date used for upcoming window (e.g. +3 days)"
          },
          "results": {
            "type": "object",
            "properties": {
              "upcoming": {
                "type": "object",
                "properties": {
                  "processed": {
                    "type": "integer"
                  },
                  "emailSent": {
                    "type": "integer"
                  },
                  "smsSent": {
                    "type": "integer"
                  },
                  "failed": {
                    "type": "integer"
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "itemId": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              },
              "overdue": {
                "type": "object",
                "properties": {
                  "processed": {
                    "type": "integer"
                  },
                  "emailSent": {
                    "type": "integer"
                  },
                  "smsSent": {
                    "type": "integer"
                  },
                  "failed": {
                    "type": "integer"
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "itemId": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "TemplateGenerateRequest": {
        "type": "object",
        "required": [
          "clientId"
        ],
        "dependentRequired": {
          "eventId": [
            "caseId"
          ]
        },
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client ID (must match case when caseId is set)"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional case ID; when set",
            "generated document is attached to this case": null
          },
          "mergeContextTeamId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional UUID. Team whose practice/attorney merge values are used; must be an active membership and share the billing root of the case/client context. Template lookup remains scoped to the active/billing team."
          },
          "eventId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional UUID for event merge fields; requires caseId (see dependentRequired)."
          },
          "mergeFieldOverrides": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional override values for merge field keys"
          },
          "useAI": {
            "type": "boolean",
            "default": false,
            "description": "When true, the server attempts AI to fill unresolved merge fields, which may add ~40s latency. The server may reject this flag when template placeholder guardrails are exceeded (more than 200 placeholders or any placeholder name longer than 256 characters)."
          },
          "previewOnly": {
            "type": "boolean",
            "default": false,
            "description": "When true and ONLYOFFICE is configured, returns a temp preview (open in browser editor) instead of creating a CaseDocument immediately when caseId is set, or attaches pendingCaseId for finalize. Returns 400 if the document server is not configured."
          },
          "mergeCaseSlotOrder": {
            "type": "array",
            "maxItems": 25,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Client-only multi-case generate (omit caseId). Ordered case UUIDs for template placeholders case_1_*, case_2_*, …; every id must belong to the client. Omitted or empty uses oldest-first createdAt order. Ignored when caseId is set."
          }
        }
      },
      "MergeFieldPreviewValue": {
        "description": "Merge field value in GET /api/templates/{id}/preview-fields JSON (matches server merge map types string|number|boolean|null; undefined keys are omitted).",
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "number"
          },
          {
            "type": "boolean"
          },
          {
            "enum": [
              null
            ]
          }
        ]
      },
      "MergeFieldsPreviewMap": {
        "type": "object",
        "description": "Map of merge field name to preview value.",
        "additionalProperties": {
          "$ref": "#/components/schemas/MergeFieldPreviewValue"
        }
      },
      "TemplatePreviewFieldsResponse": {
        "type": "object",
        "description": "GET /api/templates/{id}/preview-fields success body.",
        "required": [
          "mergeFields"
        ],
        "properties": {
          "mergeFields": {
            "$ref": "#/components/schemas/MergeFieldsPreviewMap"
          },
          "catalogMergeFields": {
            "description": "Present when includeFullCatalog=true and a fields filter is used. Same shape as merge field maps elsewhere; computed server-side as catalogMergeFields = { ...canonicalMergeFields, ...storedMergeFieldOverrides } (canonical values with stored template merge overrides layered on) for catalog/sidebar UIs.",
            "allOf": [
              {
                "$ref": "#/components/schemas/MergeFieldsPreviewMap"
              }
            ]
          }
        }
      },
      "TemplateGenerateResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "url",
              "filename",
              "size"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid",
                "description": "Present when document is attached to a case; created case document ID"
              },
              "url": {
                "type": "string",
                "description": "Download URL (app path when id present",
                "temp path for client-only or preview)": null
              },
              "filename": {
                "type": "string"
              },
              "size": {
                "type": "integer"
              },
              "previewOnly": {
                "type": "boolean",
                "description": "True when the user should open the Word editor before saving to the case"
              },
              "tempToken": {
                "type": "string",
                "format": "uuid",
                "description": "Temp row id for preview editor and finalize"
              },
              "documentServerUrl": {
                "type": "string",
                "description": "ONLYOFFICE Document Server base URL (previewOnly responses)"
              },
              "pendingCaseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Target case when finalize does not require a body caseId"
              }
            }
          }
        }
      },
      "TempFinalizeRequest": {
        "type": "object",
        "description": "Optional body when the preview row has no pendingCaseId; otherwise may be empty.",
        "properties": {
          "caseId": {
            "type": "string",
            "format": "uuid",
            "description": "Case to attach the generated document to (required when pendingCaseId is not set on the temp row)"
          }
        }
      },
      "TempFinalizeResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "caseId": {
                "type": "string",
                "format": "uuid"
              },
              "url": {
                "type": "string"
              },
              "filename": {
                "type": "string"
              },
              "size": {
                "type": "integer"
              }
            }
          }
        }
      },
      "TempPreviewDiscardResponse": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          }
        }
      },
      "TemplateUpdate": {
        "type": "object",
        "description": "PATCH /api/templates/{id} body. Mirrors documentTemplatePatchSchema — include at least one property.",
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "nullable": true,
            "maxLength": 2000
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "tags": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        }
      },
      "DocumentTemplatePatchItem": {
        "type": "object",
        "description": "Template metadata returned after PATCH (subset of columns).",
        "required": [
          "id",
          "name",
          "originalName",
          "size",
          "tags",
          "createdAt",
          "updatedAt",
          "isFromParentTeam"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "originalName": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "mergeFields": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Detected merge field names from the .docx when present."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "isFromParentTeam": {
            "type": "boolean",
            "description": "Always false on PATCH success (only the owning team may update)."
          }
        }
      },
      "DocumentTemplatePatchResponse": {
        "type": "object",
        "required": [
          "template"
        ],
        "properties": {
          "template": {
            "$ref": "#/components/schemas/DocumentTemplatePatchItem"
          }
        }
      },
      "UnreadThreadsResponse": {
        "type": "object",
        "description": "Unread message count and paginated SMS threads for the team",
        "required": [
          "unreadCount",
          "threads",
          "pagination",
          "twilioConfigured"
        ],
        "properties": {
          "unreadCount": {
            "type": "integer"
          },
          "threads": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          },
          "twilioConfigured": {
            "type": "boolean",
            "description": "Whether Twilio is configured for the firm (billing team). When false, threads are empty and unreadCount is 0."
          }
        }
      },
      "UnreadMessagesRequest": {
        "type": "object",
        "required": [
          "phoneNumberId",
          "toNumber"
        ],
        "properties": {
          "phoneNumberId": {
            "type": "string",
            "minLength": 1,
            "description": "Team phone number ID for the conversation"
          },
          "toNumber": {
            "type": "string",
            "minLength": 1,
            "description": "Other party phone number (conversation identifier)"
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional team override for messenger-local team tabs (omit to use session team)."
          }
        }
      },
      "UnreadMessagesResponse": {
        "type": "object",
        "required": [
          "success",
          "markedUnread"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "markedUnread": {
            "type": "integer",
            "description": "Number of messages marked unread"
          }
        }
      },
      "SuccessResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success"
        ]
      },
      "ApiKey": {
        "type": "object",
        "description": "API key (list view; no raw key).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "keyPrefix": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "read_write"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "SignInRequest": {
        "type": "object",
        "required": [
          "identifier",
          "password"
        ],
        "properties": {
          "identifier": {
            "type": "string",
            "description": "Email or username"
          },
          "password": {
            "type": "string",
            "description": "Password"
          }
        }
      },
      "SignInResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "ForgotPasswordRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Account email address"
          },
          "redirect": {
            "type": "string",
            "maxLength": 2048,
            "description": "Optional safe path to redirect after reset (e.g. /dashboard)"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "age": {
            "type": "integer",
            "nullable": true
          },
          "reportingAccess": {
            "type": "boolean",
            "description": "Legacy flag for reports UI (may appear on session payloads)"
          },
          "platformAccess": {
            "type": "boolean",
            "description": "DB-only; internal platform tools when true (session payloads)"
          }
        },
        "description": "Session user (stripped for API)"
      },
      "UserProfileUpdate": {
        "type": "object",
        "minProperties": 1,
        "description": "At least one property required. Omit a key to leave it unchanged; use null on firstName/lastName to clear.",
        "properties": {
          "username": {
            "type": "string",
            "minLength": 3,
            "maxLength": 31
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          }
        }
      },
      "UserProfileUpdateResponse": {
        "type": "object",
        "required": [
          "success",
          "user"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "user": {
            "type": "object",
            "required": [
              "id",
              "username",
              "firstName",
              "lastName"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "username": {
                "type": "string"
              },
              "firstName": {
                "type": "string",
                "nullable": true
              },
              "lastName": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "ClientPhoneEntry": {
        "type": "object",
        "required": [
          "phone",
          "label"
        ],
        "properties": {
          "phone": {
            "type": "string",
            "description": "E.164 format"
          },
          "label": {
            "type": "string",
            "maxLength": 50
          }
        }
      },
      "Client": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientPhoneEntry"
            }
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string",
            "nullable": true
          },
          "state": {
            "type": "string",
            "nullable": true
          },
          "zipCode": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "legalName": {
            "type": "string",
            "nullable": true
          },
          "preferredName": {
            "type": "string",
            "nullable": true,
            "maxLength": 200
          },
          "pronouns": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "description": "General profile note for the client (stored as a ClientNote with subject null). Use GET/POST /api/clients/{id}/notes for full note list."
          },
          "customFields": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClientByPhoneClient": {
        "type": "object",
        "description": "Minimal client shape returned by GET /api/clients/by-phone.",
        "required": [
          "id",
          "firstName",
          "lastName"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "preferredName": {
            "type": "string",
            "nullable": true
          },
          "pronouns": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ClientByPhoneResponse": {
        "type": "object",
        "description": "Response payload for GET /api/clients/by-phone.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "client"
            ],
            "properties": {
              "client": {
                "nullable": true,
                "$ref": "#/components/schemas/ClientByPhoneClient"
              }
            }
          }
        }
      },
      "ClientInput": {
        "type": "object",
        "required": [
          "firstName",
          "lastName"
        ],
        "properties": {
          "firstName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientPhoneEntry"
            }
          },
          "address": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "city": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "state": {
            "type": "string",
            "maxLength": 50,
            "nullable": true
          },
          "zipCode": {
            "type": "string",
            "maxLength": 20,
            "nullable": true
          },
          "country": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "legalName": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "preferredName": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "pronouns": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "source": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "description": "General profile note (persisted as ClientNote with subject null). Omit or leave empty to leave unchanged on update."
          },
          "customFields": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "Case": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "caseNumber": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "maxLength": 64,
            "description": "Firm-configured case workflow status (default set includes potential, active, closed)."
          },
          "caseType": {
            "type": "string",
            "nullable": true
          },
          "opposingParty": {
            "type": "string",
            "nullable": true
          },
          "opposingCounsel": {
            "type": "string",
            "nullable": true
          },
          "judge": {
            "type": "string",
            "nullable": true
          },
          "court": {
            "type": "string",
            "nullable": true
          },
          "county": {
            "type": "string",
            "nullable": true
          },
          "courtType": {
            "type": "string",
            "nullable": true
          },
          "charges": {
            "type": "string",
            "nullable": true
          },
          "prosecutor": {
            "type": "string",
            "nullable": true
          },
          "prosecutorEmail": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "prosecutorOffice": {
            "type": "string",
            "nullable": true
          },
          "plaintiff": {
            "type": "string",
            "nullable": true
          },
          "webexLink": {
            "type": "string",
            "nullable": true
          },
          "billingRate": {
            "type": "number",
            "nullable": true
          },
          "estimatedValue": {
            "type": "number",
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CaseInput": {
        "type": "object",
        "required": [
          "clientId"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "caseNumber": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "title": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional. On create, title is server-generated (LastName, FirstName - Case# - count) and request value is ignored."
          },
          "description": {
            "type": "string",
            "maxLength": 10000,
            "nullable": true
          },
          "status": {
            "type": "string",
            "maxLength": 64,
            "description": "Firm-configured case workflow status."
          },
          "caseType": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "opposingParty": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "opposingCounsel": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "judge": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "court": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "county": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "courtType": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "charges": {
            "type": "string",
            "maxLength": 10000,
            "nullable": true
          },
          "prosecutor": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "prosecutorEmail": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "prosecutorOffice": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "plaintiff": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "webexLink": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "billingRate": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "estimatedValue": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "CaseEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Team identifier for tenancy and template scoping"
          },
          "eventType": {
            "type": "string",
            "enum": [
              "court_date",
              "deadline",
              "meeting",
              "communication",
              "filing",
              "document",
              "billing",
              "task",
              "drive_time",
              "trial",
              "other"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "eventDate": {
            "type": "string",
            "format": "date-time"
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "allDay": {
            "type": "boolean"
          },
          "location": {
            "type": "string",
            "nullable": true
          },
          "videoLink": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "rescheduled"
            ]
          },
          "completed": {
            "type": "boolean"
          },
          "billable": {
            "type": "boolean"
          },
          "sendEmailReminder": {
            "type": "boolean"
          },
          "sendEmailReminderBefore": {
            "type": "boolean"
          },
          "emailReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "sendSmsOnCreation": {
            "type": "boolean"
          },
          "smsOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendSmsReminder": {
            "type": "boolean"
          },
          "smsReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "smsReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDateTimeChange": {
            "type": "boolean"
          },
          "sendSmsOnDateTimeChange": {
            "type": "boolean"
          },
          "smsOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDelete": {
            "type": "boolean"
          },
          "sendSmsOnDelete": {
            "type": "boolean"
          },
          "smsOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sharedWithClient": {
            "type": "boolean"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "nullable": true
          },
          "hourlyRate": {
            "type": "number",
            "nullable": true
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          },
          "assignedToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional assignee (team member ID) for tasks"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClientCaseEventAssignee": {
        "type": "object",
        "nullable": true,
        "description": "Team member assignee for tasks; same shape as web client timeline.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "User": {
            "type": "object",
            "properties": {
              "username": {
                "type": "string"
              }
            }
          }
        }
      },
      "ClientCaseEventWithRelations": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CaseEvent"
          },
          {
            "type": "object",
            "required": [
              "Case"
            ],
            "properties": {
              "AssignedToTeamMember": {
                "$ref": "#/components/schemas/ClientCaseEventAssignee"
              },
              "Case": {
                "type": "object",
                "required": [
                  "id",
                  "title"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ]
      },
      "ClientCaseEventsResponse": {
        "type": "object",
        "required": [
          "events",
          "pagination"
        ],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientCaseEventWithRelations"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          },
          "nextCursor": {
            "type": "string",
            "minLength": 1,
            "description": "Present when paginating with cursor; pass as the cursor query param for the next page (min length 1 when present).",
            "nullable": true
          }
        }
      },
      "ClientBulkCaseStatusRequest": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "Target workflow status; must be allowed for the team (same values as case status pickers)."
          }
        }
      },
      "ClientBulkCaseStatusResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "updatedCount",
              "skippedAlreadyStatus",
              "skippedNoAccess",
              "status"
            ],
            "properties": {
              "updatedCount": {
                "type": "integer",
                "minimum": 0,
                "description": "Cases whose status was changed to the requested value."
              },
              "skippedAlreadyStatus": {
                "type": "integer",
                "minimum": 0,
                "description": "Cases that already had the requested status."
              },
              "skippedNoAccess": {
                "type": "integer",
                "minimum": 0,
                "description": "Cases that needed a change but the caller cannot update (e.g. not assigned)."
              },
              "status": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "The status that was applied to updated cases."
              }
            }
          }
        }
      },
      "CaseEventInput": {
        "type": "object",
        "required": [
          "eventType",
          "title",
          "eventDate"
        ],
        "properties": {
          "eventType": {
            "type": "string",
            "enum": [
              "court_date",
              "deadline",
              "meeting",
              "communication",
              "filing",
              "document",
              "billing",
              "task",
              "drive_time",
              "trial",
              "other"
            ]
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "eventDate": {
            "type": "string",
            "format": "date-time"
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "allDay": {
            "type": "boolean"
          },
          "location": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "videoLink": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "rescheduled"
            ]
          },
          "completed": {
            "type": "boolean"
          },
          "billable": {
            "type": "boolean"
          },
          "sendEmailReminder": {
            "type": "boolean"
          },
          "sendEmailReminderBefore": {
            "type": "boolean"
          },
          "emailReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "sendSmsOnCreation": {
            "type": "boolean"
          },
          "smsOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendSmsReminder": {
            "type": "boolean"
          },
          "smsReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "smsReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDateTimeChange": {
            "type": "boolean"
          },
          "sendSmsOnDateTimeChange": {
            "type": "boolean"
          },
          "smsOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDelete": {
            "type": "boolean"
          },
          "sendSmsOnDelete": {
            "type": "boolean"
          },
          "smsOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sharedWithClient": {
            "type": "boolean"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "nullable": true
          },
          "hourlyRate": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Optional custom field values (string, number, boolean, null, string[], or boolean[] per key, matching Zod). UTF-8 byte length of JSON.stringify(customFields) must be less than 50000."
          },
          "assignedToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional assignee (team member ID) for tasks"
          },
          "messageTemplateMergeOverrides": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 2000
            },
            "description": "Optional manual values for SMS/email template placeholders on immediate send (on create)",
            "maxProperties": 40
          }
        }
      },
      "MessageTemplateEventDraft": {
        "type": "object",
        "required": [
          "title",
          "eventDate",
          "eventType"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "eventDate": {
            "type": "string",
            "format": "date-time"
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "eventType": {
            "type": "string",
            "enum": [
              "court_date",
              "deadline",
              "meeting",
              "communication",
              "filing",
              "document",
              "billing",
              "task",
              "drive_time",
              "trial",
              "other"
            ]
          },
          "location": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "description": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "allDay": {
            "type": "boolean",
            "description": "Optional; defaults to false when omitted"
          },
          "customFields": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Optional (same shape as CaseEventInput.customFields). UTF-8 byte length of JSON.stringify(customFields) must be less than 50000 (same limit as case event create)."
          }
        }
      },
      "FirmEventCreateInput": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CaseEventInput"
          },
          {
            "type": "object",
            "required": [
              "caseId"
            ],
            "properties": {
              "caseId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "CaseEventUpdate": {
        "type": "object",
        "description": "Partial update; only provided fields are updated.",
        "properties": {
          "eventType": {
            "type": "string",
            "enum": [
              "court_date",
              "deadline",
              "meeting",
              "communication",
              "filing",
              "document",
              "billing",
              "task",
              "drive_time",
              "trial",
              "other"
            ]
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "eventDate": {
            "type": "string",
            "format": "date-time"
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "allDay": {
            "type": "boolean"
          },
          "location": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "videoLink": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "completed",
              "cancelled",
              "rescheduled"
            ]
          },
          "completed": {
            "type": "boolean"
          },
          "billable": {
            "type": "boolean"
          },
          "sendEmailReminder": {
            "type": "boolean"
          },
          "sendEmailReminderBefore": {
            "type": "boolean"
          },
          "emailReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "sendSmsOnCreation": {
            "type": "boolean"
          },
          "smsOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendSmsReminder": {
            "type": "boolean"
          },
          "smsReminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "nullable": true
          },
          "smsReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDateTimeChange": {
            "type": "boolean"
          },
          "sendSmsOnDateTimeChange": {
            "type": "boolean"
          },
          "smsOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sendEmailOnDelete": {
            "type": "boolean"
          },
          "sendSmsOnDelete": {
            "type": "boolean"
          },
          "smsOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sharedWithClient": {
            "type": "boolean"
          },
          "assignedToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional assignee (team member ID) for tasks"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "nullable": true
          },
          "hourlyRate": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true,
            "description": "Same shape and UTF-8 JSON size limit as CaseEventInput.customFields (less than 50000 bytes for JSON.stringify)."
          }
        }
      },
      "TimeEntryActivityFieldsRule": {
        "description": "Pairwise contract for automatic-entry fields: both JSON null (manual entry) or both non-empty strings (dedupe key scoped per team in the database). Mixed null/non-null is invalid.",
        "oneOf": [
          {
            "title": "Manual entry",
            "required": [
              "activitySource",
              "activitySourceId"
            ],
            "properties": {
              "activitySource": {
                "type": "string",
                "nullable": true
              },
              "activitySourceId": {
                "type": "string",
                "nullable": true
              }
            }
          },
          {
            "title": "Automatic entry",
            "required": [
              "activitySource",
              "activitySourceId"
            ],
            "properties": {
              "activitySource": {
                "type": "string",
                "minLength": 1
              },
              "activitySourceId": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        ]
      },
      "TimeEntry": {
        "allOf": [
          {
            "type": "object",
            "description": "Billable time row; activitySource/activitySourceId follow TimeEntryActivityFieldsRule.",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "caseId": {
                "type": "string",
                "format": "uuid"
              },
              "description": {
                "type": "string"
              },
              "date": {
                "type": "string",
                "format": "date-time"
              },
              "duration": {
                "type": "integer",
                "minimum": 1
              },
              "hourlyRate": {
                "type": "number",
                "nullable": true
              },
              "amount": {
                "type": "number",
                "minimum": 0,
                "nullable": true
              },
              "billable": {
                "type": "boolean"
              },
              "notes": {
                "type": "string",
                "nullable": true
              },
              "activitySource": {
                "type": "string",
                "nullable": true,
                "description": "With activitySourceId: both null (manual) or both set (non-empty). Automatic values include sms_outbound, call_completed, document_generated, ir_report, case_event."
              },
              "activitySourceId": {
                "type": "string",
                "nullable": true,
                "description": "With activitySource: both null (manual) or both set. Together with teamId, forms the unique dedupe key for automatic entries."
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          {
            "$ref": "#/components/schemas/TimeEntryActivityFieldsRule"
          }
        ]
      },
      "TimeEntryInput": {
        "type": "object",
        "required": [
          "caseId",
          "description",
          "date",
          "duration"
        ],
        "properties": {
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000
          },
          "date": {
            "type": "string",
            "format": "date-time"
          },
          "duration": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440
          },
          "hourlyRate": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "billable": {
            "type": "boolean"
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client this invoice belongs to"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Primary case (single-case invoice); null for multi-case or client-level"
          },
          "paymentPlanItemId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Present when this invoice was generated from a payment plan installment"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "overdue",
              "cancelled"
            ]
          },
          "issueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "taxRate": {
            "type": "number",
            "nullable": true
          },
          "discountAmount": {
            "type": "number",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "billingMode": {
            "type": "string",
            "enum": [
              "standard",
              "retainer_statement"
            ],
            "description": "retainer_statement is a non-payable activity statement: optional caseId for one matter or omit caseId for client-wide (non-empty caseIds not allowed). Notes include auto-generated deposit snapshot block(s). standard is a normal payable invoice."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceItem"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the invoice was marked paid (set by the app when status is paid)"
          }
        }
      },
      "InvoiceItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional case attribution for multi-case invoices"
          },
          "timeEntryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "feeType": {
            "type": "string",
            "enum": [
              "hourly",
              "flat"
            ]
          },
          "quantity": {
            "type": "number",
            "nullable": true
          },
          "rate": {
            "type": "number"
          }
        }
      },
      "InvoiceItemInput": {
        "type": "object",
        "description": "Line item for POST /api/invoices (create). Matches invoiceItemSchema (no id). For billingMode standard the server requires non-empty description, positive rate, and quantity when feeType is hourly. For billingMode retainer_statement each line must include timeEntryId (amounts resolved server-side).",
        "properties": {
          "timeEntryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "description": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "feeType": {
            "type": "string",
            "enum": [
              "hourly",
              "flat"
            ],
            "default": "hourly"
          },
          "quantity": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 999999.99,
            "nullable": true,
            "description": "Required when feeType is hourly (standard invoices)"
          },
          "rate": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 999999.99,
            "nullable": true,
            "description": "Required for standard invoices; optional for retainer_statement lines with timeEntryId only"
          }
        }
      },
      "InvoiceInputCommon": {
        "type": "object",
        "description": "Shared POST /api/invoices fields (case scope and billing mode validated in InvoiceInput oneOf)",
        "required": [
          "clientId",
          "invoiceNumber",
          "items"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client this invoice belongs to"
          },
          "invoiceNumber": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "overdue",
              "cancelled"
            ]
          },
          "issueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "taxRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "nullable": true
          },
          "discountAmount": {
            "type": "number",
            "minimum": 0,
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "sendEmail": {
            "type": "boolean"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/InvoiceItemInput"
            }
          }
        }
      },
      "RetainerDepositPayload": {
        "type": "object",
        "description": "Shared deposit fields (amount, optional received date, optional memo) for invoice payloads and deposit APIs.",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 999999.99,
            "multipleOf": 0.01,
            "description": "Dollar amount in whole cents (multipleOf 0.01); fractional cents are rejected server-side."
          },
          "receivedDate": {
            "nullable": true,
            "description": "Optional calendar date (YYYY-MM-DD), ISO 8601 datetime, empty string, or null; blank/null is treated as omitted and the server defaults receivedDate to now. Responses use a stable YYYY-MM-DD (UTC calendar).",
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "string",
                "maxLength": 0,
                "description": "Empty string (same as omitted)"
              }
            ]
          },
          "idempotencyKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Optional duplicate-suppression key; prefer the Idempotency-Key HTTP header when both are sent."
          },
          "memo": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          }
        }
      },
      "InvoiceWithRetainerDeposit": {
        "description": "Optional on POST /api/invoices when billingMode is retainer_statement; creates the deposit in the same transaction as the invoice. When the invoice has a top-level caseId, deposit caseId must match; for client-wide statements (no invoice caseId), deposit caseId is any matter for that client.",
        "allOf": [
          {
            "$ref": "#/components/schemas/RetainerDepositPayload"
          },
          {
            "type": "object",
            "required": [
              "caseId"
            ],
            "properties": {
              "caseId": {
                "type": "string",
                "format": "uuid",
                "description": "Matter that receives the deposit; must match invoice caseId when the statement is single-matter"
              }
            }
          }
        ]
      },
      "InvoiceInput": {
        "oneOf": [
          {
            "title": "Retainer activity statement",
            "description": "billingMode must be retainer_statement. Optional caseId for a single matter, or omit caseId for a client-wide statement (do not send non-empty caseIds). Server prepends deposit snapshot block(s) to notes. Each line item must include timeEntryId only (amounts resolved server-side). Duplicate timeEntryId values across items are rejected. Optional withRetainerDeposit records a deposit in the same transaction.",
            "allOf": [
              {
                "$ref": "#/components/schemas/InvoiceInputCommon"
              },
              {
                "type": "object",
                "required": [
                  "billingMode"
                ],
                "properties": {
                  "billingMode": {
                    "type": "string",
                    "enum": [
                      "retainer_statement"
                    ]
                  },
                  "caseId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Single matter for this statement when set; omit for client-wide (mutually exclusive with non-empty caseIds)"
                  },
                  "withRetainerDeposit": {
                    "$ref": "#/components/schemas/InvoiceWithRetainerDeposit"
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "timeEntryId"
                      ],
                      "properties": {
                        "timeEntryId": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              },
              {
                "not": {
                  "type": "object",
                  "required": [
                    "caseIds"
                  ],
                  "description": "caseIds must not appear on retainer_statement payloads (use caseId or omit for client-wide)."
                }
              }
            ]
          },
          {
            "title": "Standard / multi-case / client-level",
            "description": "billingMode omitted or standard. At most one of caseId or non-empty caseIds (mutually exclusive); validated server-side.",
            "allOf": [
              {
                "$ref": "#/components/schemas/InvoiceInputCommon"
              },
              {
                "type": "object",
                "properties": {
                  "billingMode": {
                    "type": "string",
                    "enum": [
                      "standard"
                    ],
                    "default": "standard"
                  },
                  "caseId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Single case (use exactly one of caseId, caseIds, or neither for client-level)"
                  },
                  "caseIds": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Multiple cases (multi-case invoice); use when not caseId and not client-level"
                  }
                }
              },
              {
                "not": {
                  "type": "object",
                  "required": [
                    "caseId",
                    "caseIds"
                  ]
                }
              },
              {
                "not": {
                  "type": "object",
                  "required": [
                    "withRetainerDeposit"
                  ],
                  "description": "withRetainerDeposit is only valid for retainer_statement (see other oneOf branch)."
                }
              }
            ]
          }
        ]
      },
      "RetainerDepositCreate": {
        "description": "POST /api/cases/{id}/retainer-deposits body; case id is the path parameter (not repeated here). Amount uses whole cents (multipleOf 0.01); fractional cents are rejected server-side.",
        "allOf": [
          {
            "$ref": "#/components/schemas/RetainerDepositPayload"
          }
        ]
      },
      "RetainerDepositRecord": {
        "type": "object",
        "required": [
          "id",
          "amount",
          "receivedDate",
          "memo",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "number"
          },
          "receivedDate": {
            "type": "string",
            "format": "date",
            "description": "Calendar received date YYYY-MM-DD (UTC)"
          },
          "memo": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RetainerDepositCreateSuccessData": {
        "type": "object",
        "required": [
          "created",
          "deposit",
          "summary"
        ],
        "properties": {
          "created": {
            "type": "boolean",
            "description": "False when the request reused an existing deposit (same Idempotency-Key); true when a new row was inserted."
          },
          "deposit": {
            "$ref": "#/components/schemas/RetainerDepositRecord"
          },
          "summary": {
            "$ref": "#/components/schemas/CaseRetainerSummary"
          }
        }
      },
      "RetainerDepositCreateResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/RetainerDepositCreateSuccessData"
          }
        }
      },
      "CaseRetainerSummarySuccessResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CaseRetainerSummary"
          }
        }
      },
      "CaseRetainerSummary": {
        "type": "object",
        "required": [
          "depositsTotal",
          "feesEarnedBillable",
          "balance",
          "recentDeposits"
        ],
        "properties": {
          "depositsTotal": {
            "type": "number"
          },
          "feesEarnedBillable": {
            "type": "number"
          },
          "balance": {
            "type": "number"
          },
          "recentDeposits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RetainerDepositRecord"
            }
          }
        }
      },
      "PortalInvoiceListItem": {
        "type": "object",
        "description": "Invoice row returned by GET /api/portal/invoices",
        "required": [
          "id",
          "invoiceNumber",
          "billingMode",
          "status",
          "issueDate",
          "total",
          "amountDue"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "billingMode": {
            "type": "string",
            "enum": [
              "standard",
              "retainer_statement"
            ],
            "description": "retainer_statement is a retainer activity statement (non-payable summary), optionally tied to one matter or client-wide; standard is a normal invoice"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "overdue",
              "cancelled"
            ]
          },
          "issueDate": {
            "type": "string",
            "format": "date-time"
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "total": {
            "type": "number"
          },
          "amountDue": {
            "type": "number"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "Case": {
            "type": "object",
            "nullable": true,
            "required": [
              "id",
              "title"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "title": {
                "type": "string"
              },
              "caseNumber": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "InvoiceUpdate": {
        "oneOf": [
          {
            "description": "Payment plan sync branch: requires paymentPlanSync and at least one of total, taxRate, or discountAmount. Do not send items together with paymentPlanSync — updateInvoiceSchema rejects that pair (line-item replace uses a different path). additionalProperties false omits items here so OpenAPI matches runtime.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "paymentPlanSync"
            ],
            "allOf": [
              {
                "anyOf": [
                  {
                    "required": [
                      "total"
                    ]
                  },
                  {
                    "required": [
                      "taxRate"
                    ]
                  },
                  {
                    "required": [
                      "discountAmount"
                    ]
                  }
                ]
              }
            ],
            "properties": {
              "clientId": {
                "type": "string",
                "format": "uuid"
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "caseIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "invoiceNumber": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "status": {
                "type": "string",
                "enum": [
                  "draft",
                  "sent",
                  "paid",
                  "overdue",
                  "cancelled"
                ]
              },
              "issueDate": {
                "type": "string",
                "format": "date-time",
                "description": "Omit to leave unchanged; ISO datetime only (null not accepted)."
              },
              "dueDate": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "taxRate": {
                "type": "number",
                "minimum": 0,
                "maximum": 1,
                "nullable": true
              },
              "discountAmount": {
                "type": "number",
                "minimum": 0,
                "nullable": true
              },
              "notes": {
                "type": "string",
                "maxLength": 5000,
                "nullable": true
              },
              "sendEmail": {
                "type": "boolean"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time",
                "description": "When marking paid or correcting paid date; ISO datetime (app often sends local calendar day at noon)."
              },
              "total": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true,
                "maximum": 999999.99,
                "multipleOf": 0.01,
                "description": "Override final total without replacing items (whole cents). With a non-cancelled payment plan, send paymentPlanSync when the new total exceeds current installment principal (this branch always sends a billing field). Metadata-only PUTs (no total/items/tax/discount) do not require paymentPlanSync for historical invoice-vs-principal drift. Cancelled plans allow invoice-only changes without paymentPlanSync."
              },
              "paymentPlanSync": {
                "type": "object",
                "required": [
                  "mode",
                  "dueDate"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "appendPrincipal"
                    ]
                  },
                  "dueDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Due date for the new installment covering the principal increase."
                  }
                }
              }
            }
          },
          {
            "description": "Standard partial update without paymentPlanSync. Do not send paymentPlanSync (omitted from properties; additionalProperties false). Runtime forbids sending both total and items together.",
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "clientId": {
                "type": "string",
                "format": "uuid"
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "caseIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "invoiceNumber": {
                "type": "string",
                "minLength": 1,
                "maxLength": 100
              },
              "status": {
                "type": "string",
                "enum": [
                  "draft",
                  "sent",
                  "paid",
                  "overdue",
                  "cancelled"
                ]
              },
              "issueDate": {
                "type": "string",
                "format": "date-time",
                "description": "Omit to leave unchanged; ISO datetime only (null not accepted)."
              },
              "dueDate": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "taxRate": {
                "type": "number",
                "minimum": 0,
                "maximum": 1,
                "nullable": true
              },
              "discountAmount": {
                "type": "number",
                "minimum": 0,
                "nullable": true
              },
              "notes": {
                "type": "string",
                "maxLength": 5000,
                "nullable": true
              },
              "sendEmail": {
                "type": "boolean"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time",
                "description": "When marking paid or correcting paid date; ISO datetime (app often sends local calendar day at noon)."
              },
              "total": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true,
                "maximum": 999999.99,
                "multipleOf": 0.01,
                "description": "Override final total without replacing items (omit when sending items). Whole cents; max 999999.99."
              },
              "items": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/InvoiceItem"
                }
              }
            }
          }
        ]
      },
      "PaymentPlan": {
        "type": "object",
        "description": "Payment plan with installments",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client this payment plan belongs to"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Primary case; null for client-level or multi-case"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Linked invoice when set; null only on legacy data before invoice linking was required"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "totalAmount": {
            "type": "number",
            "minimum": 0.01,
            "description": "Total principal for the plan only. Per-installment late fees (items[].lateFeeAmount) are excluded from this total and from validation that compares totalAmount to the sum of item principals."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "cancelled"
            ]
          },
          "startDate": {
            "type": "string",
            "format": "date-time"
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentPlanItem"
            }
          }
        }
      },
      "PaymentPlanInstallmentReplaceItemInput": {
        "description": "One installment row for POST /api/payment-plans (custom items[]) or PUT /api/payment-plans/{id} when replacing items[]. Matches refinePaymentPlanInstallmentItems in src/lib/server/validation.ts: each row must have principal (amount) or lateFeeAmount strictly positive. If lateFeeAssessedDate is sent (non-null), the server requires lateFeeAmount &gt; 0 for that row (OpenAPI 3.0 has no if/then; clients must follow the same rule the API enforces).",
        "allOf": [
          {
            "type": "object",
            "required": [
              "amount",
              "dueDate"
            ],
            "properties": {
              "amount": {
                "type": "number",
                "minimum": 0,
                "maximum": 999999.99
              },
              "dueDate": {
                "type": "string",
                "format": "date-time"
              },
              "lateFeeAmount": {
                "type": "number",
                "minimum": 0,
                "maximum": 999999.99,
                "description": "Optional per-installment late fee; omitted is treated as 0 at runtime. Must be &gt; 0 when lateFeeAssessedDate is set (see refinePaymentPlanInstallmentItems)."
              },
              "lateFeeAssessedDate": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "When the late fee was assessed. Only valid when lateFeeAmount is positive; enforced by refinePaymentPlanInstallmentItems on the server."
              }
            }
          },
          {
            "anyOf": [
              {
                "type": "object",
                "description": "Positive principal installment.",
                "properties": {
                  "amount": {
                    "type": "number",
                    "minimum": 0.01,
                    "maximum": 999999.99
                  }
                },
                "required": [
                  "amount"
                ]
              },
              {
                "type": "object",
                "description": "Late-fee-only row (amount may be 0).",
                "properties": {
                  "lateFeeAmount": {
                    "type": "number",
                    "minimum": 0.01,
                    "maximum": 999999.99
                  }
                },
                "required": [
                  "lateFeeAmount"
                ]
              }
            ]
          }
        ]
      },
      "PaymentPlanCreate": {
        "allOf": [
          {
            "type": "object",
            "description": "Shared fields for POST /api/payment-plans (matches paymentPlanSchema). invoiceId is required; plans must be linked to an invoice. Send either a generated schedule (numPayments) or custom items (≥1 row), not both. caseIds is optional; when 2+ distinct ids are sent, PaymentPlanCase rows are created (multi-case).",
            "required": [
              "name",
              "totalAmount",
              "invoiceId"
            ],
            "properties": {
              "clientId": {
                "type": "string",
                "format": "uuid",
                "description": "Optional; must match invoice client when provided"
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "invoiceId": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "totalAmount": {
                "type": "number",
                "minimum": 0.01,
                "description": "Total principal for the plan only. Per-installment late fees (items[].lateFeeAmount) are excluded from this total and from create-time validation that compares this field to the sum of item principals."
              },
              "startDate": {
                "type": "string",
                "format": "date-time"
              },
              "notes": {
                "type": "string",
                "maxLength": 5000,
                "nullable": true
              },
              "downPayment": {
                "type": "number",
                "minimum": 0,
                "default": 0
              },
              "frequency": {
                "type": "string",
                "enum": [
                  "weekly",
                  "biweekly",
                  "monthly",
                  "quarterly"
                ],
                "default": "monthly"
              },
              "sendEmail": {
                "type": "boolean",
                "default": false,
                "description": "Email client on create using firm billing sender"
              },
              "caseIds": {
                "type": "array",
                "minItems": 1,
                "maxItems": 120,
                "uniqueItems": true,
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Optional multi-case links; entries must be unique. Client/case context still resolved from invoice when omitted"
              }
            }
          },
          {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "numPayments"
                ],
                "description": "Generated installment schedule (downPayment, frequency, numPayments, startDate).",
                "properties": {
                  "numPayments": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 120
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "items"
                ],
                "description": "Explicit installment rows (amount + dueDate per item).",
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 120,
                    "items": {
                      "$ref": "#/components/schemas/PaymentPlanInstallmentReplaceItemInput"
                    }
                  }
                }
              }
            ]
          }
        ]
      },
      "PaymentPlanUpdateInput": {
        "type": "object",
        "description": "Partial payment plan update (all fields optional). Matches updatePaymentPlanSchema: do not send numPayments together with items. caseIds when present must have 1–120 entries.",
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional; must match the plan client when provided"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseIds": {
            "type": "array",
            "minItems": 1,
            "maxItems": 120,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "When present: validated (bounds, UUIDs, unique); updates PaymentPlanCase links (2+ ids) or sets primary caseId when a single id is sent"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid",
            "description": "When provided, must be a valid invoice id for the same client. Omit the field to leave the link unchanged; null is rejected by the server (plans cannot be unlinked from an invoice)."
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "totalAmount": {
            "type": "number",
            "minimum": 0.01,
            "description": "Total principal for the plan only when provided. Per-installment late fees (items[].lateFeeAmount) are excluded from this total and from validation that compares totalAmount to the sum of item principals."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed",
              "cancelled"
            ]
          },
          "startDate": {
            "type": "string",
            "format": "date-time"
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "downPayment": {
            "type": "number",
            "minimum": 0,
            "maximum": 999999.99,
            "description": "Not applied by PUT handler when sent alone (no-op for schedule regeneration)"
          },
          "frequency": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly"
            ]
          },
          "numPayments": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "description": "Must not be sent together with items"
          },
          "sendEmail": {
            "type": "boolean",
            "description": "Not used on PUT"
          },
          "items": {
            "type": "array",
            "maxItems": 120,
            "description": "Replaces installments when provided; at most 120 rows",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PaymentPlanInstallmentReplaceItemInput"
                },
                {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "scheduled",
                        "paid",
                        "overdue",
                        "cancelled",
                        "skipped"
                      ]
                    },
                    "paidDate": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "paymentMethod": {
                      "type": "string",
                      "nullable": true
                    },
                    "notes": {
                      "type": "string",
                      "maxLength": 1000,
                      "nullable": true
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "PaymentPlanItem": {
        "type": "object",
        "description": "Payment plan installment row",
        "required": [
          "id",
          "paymentPlanId",
          "amount",
          "dueDate",
          "status",
          "lateFeeAmount"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "paymentPlanId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "maximum": 999999.99,
            "description": "Principal for this installment (0 allowed for late-fee-only rows with lateFeeAmount > 0)."
          },
          "lateFeeAmount": {
            "type": "number",
            "minimum": 0,
            "maximum": 999999.99,
            "description": "Per-installment late fee; always present in API responses (0 when none). Not part of plan totalAmount."
          },
          "lateFeeAssessedDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the late fee was assessed, if recorded."
          },
          "dueDate": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "scheduled",
              "paid",
              "overdue",
              "cancelled",
              "skipped"
            ]
          },
          "paidDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          }
        }
      },
      "EmptyJsonObjectRequestBody": {
        "type": "object",
        "additionalProperties": false,
        "description": "Empty JSON object for POST endpoints that do not read a body. Clients may send {} or omit the body."
      },
      "InvoiceLineItemNumeric": {
        "type": "object",
        "description": "Invoice line item as returned by generate-invoice (quantity/rate/amount as JSON numbers).",
        "required": [
          "id",
          "invoiceId",
          "description",
          "feeType",
          "rate",
          "amount"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoiceId": {
            "type": "string",
            "format": "uuid"
          },
          "timeEntryId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "feeType": {
            "type": "string",
            "enum": [
              "hourly",
              "flat"
            ]
          },
          "quantity": {
            "type": "number",
            "nullable": true
          },
          "rate": {
            "type": "number"
          },
          "amount": {
            "type": "number"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvoiceFromPaymentPlanInstallment": {
        "type": "object",
        "description": "Draft invoice returned by POST .../generate-invoice; monetary Decimals are serialized as JSON numbers. Includes nested Client/Case when present on the created row.",
        "required": [
          "id",
          "teamId",
          "clientId",
          "invoiceNumber",
          "billingMode",
          "status",
          "subtotal",
          "total",
          "InvoiceItem"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "paymentPlanItemId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Set when this draft was generated from a payment plan installment (POST generate-invoice)."
          },
          "invoiceNumber": {
            "type": "string"
          },
          "billingMode": {
            "type": "string",
            "enum": [
              "standard",
              "retainer_statement"
            ],
            "description": "Same as Invoice / PortalInvoiceListItem: standard payable invoice or retainer activity statement."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "overdue",
              "cancelled"
            ]
          },
          "issueDate": {
            "type": "string",
            "format": "date-time"
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "subtotal": {
            "type": "number"
          },
          "taxRate": {
            "type": "number",
            "nullable": true
          },
          "taxAmount": {
            "type": "number",
            "nullable": true
          },
          "discountAmount": {
            "type": "number",
            "nullable": true
          },
          "total": {
            "type": "number"
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "portalViewCount": {
            "type": "integer"
          },
          "lastPortalViewAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "Client": {
            "type": "object",
            "nullable": true,
            "description": "Selected client fields when included"
          },
          "Case": {
            "type": "object",
            "nullable": true,
            "description": "Selected case and nested Client when included"
          },
          "InvoiceItem": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceLineItemNumeric"
            }
          }
        }
      },
      "PaymentPlanItemGenerateInvoiceResponse": {
        "type": "object",
        "required": [
          "invoice"
        ],
        "properties": {
          "invoice": {
            "$ref": "#/components/schemas/InvoiceFromPaymentPlanInstallment"
          }
        }
      },
      "PaymentPlanItemDeleteRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Optional body for DELETE /api/payment-plans/{id}/items/{itemId}",
        "properties": {
          "syncLinkedInvoiceTotal": {
            "type": "boolean",
            "description": "When true and this plan is linked to an invoice (`PaymentPlan.invoiceId`), after deleting the installment the invoice total and line items are scaled to match the new plan principal sum (draft, sent, or overdue invoices only)."
          }
        }
      },
      "PaymentPlanItemDeleteResponse": {
        "type": "object",
        "required": [
          "deleted",
          "id"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Always true when the installment was removed"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id of the deleted installment"
          },
          "syncedInvoice": {
            "type": "object",
            "description": "Present when the request included syncLinkedInvoiceTotal and the linked invoice was updated",
            "required": [
              "id",
              "total"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "total": {
                "type": "number",
                "description": "Invoice total after scaling line items to match new plan principal"
              }
            }
          }
        }
      },
      "PaymentPlanItemUpdateInput": {
        "type": "object",
        "description": "Partial fields for PUT /api/payment-plans/{id}/items/{itemId}. Due date, principal, and late fee can be updated even when the installment is already paid (e.g. corrections). The server merges with the current row (refinePaymentPlanInstallmentItems on full plan payloads; this route applies the same rules to the effective merged row): each installment must still have principal or late fee greater than 0 after merge. If you send lateFeeAssessedDate (non-null), the effective late fee after merge must be &gt; 0 (typically send lateFeeAmount &gt; 0 in the same request). OpenAPI 3.0 does not express this conditional; rely on server validation.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "scheduled",
              "paid",
              "overdue",
              "cancelled",
              "skipped"
            ]
          },
          "paidDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Only when status is paid; omit or null for non-paid statuses. When status transitions to paid and paidDate is omitted, the server defaults paidDate to the installment due date."
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 1000,
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "format": "date-time"
          },
          "amount": {
            "type": "number",
            "minimum": 0,
            "maximum": 999999.99
          },
          "lateFeeAmount": {
            "type": "number",
            "minimum": 0,
            "maximum": 999999.99,
            "description": "When setting lateFeeAssessedDate, include lateFeeAmount &gt; 0 unless the stored row already has a positive late fee (server merges then validates)."
          },
          "lateFeeAssessedDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the late fee was assessed. Only valid when the merged late fee is &gt; 0 (see refinePaymentPlanInstallmentItems)."
          }
        }
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "entityType": {
            "type": "string",
            "nullable": true
          },
          "entityId": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "client": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              }
            }
          },
          "readAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NotificationListItem": {
        "type": "object",
        "description": "Notification item as returned by GET /api/notifications.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "entityType": {
            "type": "string",
            "nullable": true
          },
          "entityId": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "client": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              }
            }
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "readAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EmailAttachment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "contentType": {
            "type": "string",
            "nullable": true
          },
          "size": {
            "type": "integer",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "EmailItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": "string",
            "format": "uuid"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "fromEmail": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "fromName": {
            "type": "string",
            "nullable": true
          },
          "toEmails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ccEmails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bccEmails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "htmlBody": {
            "type": "string",
            "nullable": true
          },
          "textBody": {
            "type": "string",
            "nullable": true
          },
          "snippet": {
            "type": "string",
            "nullable": true
          },
          "hasAttachments": {
            "type": "boolean"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailAttachment"
            }
          },
          "isRead": {
            "type": "boolean"
          },
          "isStarred": {
            "type": "boolean"
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "EmailThread": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "snippet": {
            "type": "string",
            "nullable": true
          },
          "participantEmails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isRead": {
            "type": "boolean"
          },
          "isStarred": {
            "type": "boolean"
          },
          "isArchived": {
            "type": "boolean"
          },
          "emailCount": {
            "type": "integer"
          },
          "unreadCount": {
            "type": "integer"
          },
          "lastEmailAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastEmailDirection": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EmailThreadsListResponse": {
        "type": "object",
        "properties": {
          "threads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailThread"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "EmailThreadUpdate": {
        "type": "object",
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "isArchived": {
            "type": "boolean"
          },
          "isStarred": {
            "type": "boolean"
          },
          "isRead": {
            "type": "boolean"
          }
        }
      },
      "EmailSendRequest": {
        "type": "object",
        "required": [
          "to",
          "subject",
          "html"
        ],
        "properties": {
          "to": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "subject": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "documentIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "EmailReplyRequest": {
        "type": "object",
        "required": [
          "to",
          "subject",
          "html"
        ],
        "properties": {
          "to": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "subject": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "documentIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "EmailGenerateRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "maxLength": 2000
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "EmailGenerateResponse": {
        "type": "object",
        "properties": {
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        }
      },
      "EmailStatusItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "to": {
            "type": "string",
            "nullable": true
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "lastEvent": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "entityType": {
            "type": "string",
            "nullable": true
          },
          "entityId": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string",
            "enum": [
              "stored",
              "resend"
            ]
          }
        }
      },
      "NoteAuthor": {
        "type": "object",
        "properties": {
          "username": {
            "type": "string",
            "nullable": true
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "NoteInput": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000
          },
          "noteDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "CaseNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "content": {
            "type": "string"
          },
          "noteDate": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "User": {
            "$ref": "#/components/schemas/NoteAuthor"
          }
        }
      },
      "ClientNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "content": {
            "type": "string"
          },
          "noteDate": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "User": {
            "$ref": "#/components/schemas/NoteAuthor"
          }
        }
      },
      "AddAttachmentToCaseRequest": {
        "type": "object",
        "required": [
          "caseId"
        ],
        "properties": {
          "caseId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "AddEmailAttachmentAndLinkThreadRequest": {
        "type": "object",
        "description": "Body for POST /api/cases/{id}/email-attachment. Matches addEmailAttachmentAndLinkThreadBodySchema in validation.ts.",
        "required": [
          "emailAttachmentId",
          "threadId"
        ],
        "properties": {
          "emailAttachmentId": {
            "type": "string",
            "format": "uuid"
          },
          "threadId": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Optional; must match the case client when provided"
          }
        }
      },
      "EmailAttachmentLinkedCaseDocument": {
        "type": "object",
        "description": "Case document row returned after copying an email attachment (blob url omitted from JSON).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "originalName": {
            "type": "string",
            "nullable": true
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "downloadUrl": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "mimeType": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "uploadedBy": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AddEmailAttachmentAndLinkThreadResponse": {
        "type": "object",
        "required": [
          "document"
        ],
        "properties": {
          "document": {
            "$ref": "#/components/schemas/EmailAttachmentLinkedCaseDocument"
          }
        }
      },
      "CallRecordingSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "originalName": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "size": {
            "type": "integer"
          },
          "mimeType": {
            "type": "string"
          },
          "duration": {
            "type": "integer",
            "nullable": true
          },
          "isVoicemail": {
            "type": "boolean",
            "nullable": true
          },
          "transcriptionStatus": {
            "type": "string",
            "nullable": true
          },
          "transcription": {
            "type": "string",
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          },
          "keyPoints": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "actionableItems": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "transcribedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CallRecordingTranscriptResponse": {
        "type": "object",
        "required": [
          "transcription",
          "transcriptionData",
          "transcriptionStatus"
        ],
        "properties": {
          "transcription": {
            "type": "string",
            "nullable": true
          },
          "transcriptionData": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "transcriptionStatus": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "CallRecordingsResponse": {
        "type": "object",
        "properties": {
          "recordings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "call": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "direction": {
                      "type": "string"
                    },
                    "fromNumber": {
                      "type": "string"
                    },
                    "toNumber": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "duration": {
                      "type": "integer",
                      "nullable": true
                    },
                    "startTime": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "recording": {
                  "$ref": "#/components/schemas/CallRecordingSummary"
                }
              }
            }
          }
        }
      },
      "UpdateRecordingRequest": {
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string",
            "nullable": true,
            "maxLength": 255
          }
        }
      },
      "StartTranscriptionRequest": {
        "type": "object",
        "properties": {
          "forceRegenerate": {
            "type": "boolean"
          }
        }
      },
      "NotificationReadResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "readAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          }
        }
      },
      "NotificationUnreadCountResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "unreadCount": {
                "type": "integer"
              }
            }
          }
        }
      },
      "NotificationMarkAllReadResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "markedCount": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PrepareUploadRequest": {
        "type": "object",
        "description": "JSON body for POST /api/cases/{id}/documents/prepare-upload. Matches prepareUploadSchema; when category is ir_report, irReportType is required. Effective max size depends on mimeType (see size and mimeType descriptions; FILE_SIZE_LIMITS in blob.ts).",
        "required": [
          "filename",
          "size",
          "mimeType"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 524288000,
            "description": "File size in bytes. Zod allows up to 524288000 (500 MB); the handler enforces MIME-specific caps — image/* max 10485760 (10 MB); audio/* and documents/text (e.g. application/pdf, Word, text/plain) max 52428800 (50 MB); video/* max 524288000 (500 MB)."
          },
          "mimeType": {
            "type": "string",
            "minLength": 1,
            "description": "MIME type of the file. Example caps — 10 MB: image/jpeg, image/png; 50 MB: application/pdf, audio/mpeg, audio/webm, text/plain; 500 MB: video/mp4."
          },
          "category": {
            "type": "string",
            "description": "Optional document category; must not be ir_report unless irReportType is set (server validates)."
          },
          "irReportType": {
            "type": "string",
            "enum": [
              "client_phone_call",
              "prosecutor_phone_call",
              "other_phone_call",
              "closing_call",
              "intro_call",
              "info_only",
              "post_hearing",
              "case_ready_to_close",
              "discovery_check"
            ]
          },
          "isDiscovery": {
            "type": "boolean"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "tags": {
            "type": "string",
            "description": "Comma-separated tags"
          }
        }
      },
      "PrepareUploadResponse": {
        "type": "object",
        "description": "Success body for prepare-upload; client uploads the file to Vercel Blob using pathname, then completes the flow.",
        "required": [
          "pathname",
          "documentId"
        ],
        "properties": {
          "pathname": {
            "type": "string",
            "description": "Blob pathname for client-side upload"
          },
          "documentId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CaseDocument": {
        "type": "object",
        "description": "Case document (file or IR report) with metadata.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "originalName": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          },
          "mimeType": {
            "type": "string"
          },
          "size": {
            "type": "integer"
          },
          "category": {
            "type": "string",
            "enum": [
              "general",
              "pleading",
              "discovery",
              "evidence",
              "contract",
              "correspondence",
              "filing",
              "other",
              "ir_report"
            ]
          },
          "isDiscovery": {
            "type": "boolean"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "uploadedBy": {
            "type": "string",
            "nullable": true
          },
          "summaryStatus": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed"
            ],
            "nullable": true
          },
          "summary": {
            "type": "string",
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "nullable": true,
            "description": "For IR reports, contains irReportType."
          },
          "downloadUrl": {
            "type": "string",
            "description": "Path to download endpoint (e.g. /api/documents/{id}/download)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "Versions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentVersion"
            },
            "nullable": true
          }
        }
      },
      "DocumentVersion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "originalName": {
            "type": "string"
          },
          "version": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "size": {
            "type": "integer"
          },
          "downloadUrl": {
            "type": "string"
          }
        }
      },
      "CaseDocumentUpdate": {
        "type": "object",
        "description": "Partial update for case document metadata. description and displayName accept null to clear.",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "general",
              "pleading",
              "discovery",
              "evidence",
              "contract",
              "correspondence",
              "filing",
              "other",
              "ir_report"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isDiscovery": {
            "type": "boolean"
          },
          "displayName": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "DocuSealCredentialsStatus": {
        "type": "object",
        "description": "DocuSeal integration status for the active team. configured is true if this team or an ancestor has an API key. templateFieldMaps is populated only when the caller has team.settings.update; otherwise it is an empty object.",
        "required": [
          "configured",
          "credentialsOnThisTeam",
          "url",
          "enabledAt",
          "sendEmailViaApp",
          "templateFieldMaps"
        ],
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "credentialsOnThisTeam": {
            "type": "boolean",
            "description": "True when the API key is stored on the selected team row"
          },
          "url": {
            "type": "string",
            "description": "Stored DocuSeal base URL on this team; empty string when unset"
          },
          "enabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sendEmailViaApp": {
            "type": "boolean"
          },
          "templateFieldMaps": {
            "type": "object",
            "description": "Saved per-template field maps on this team row (template id → field name → merge key or literal:text)",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        }
      },
      "DocuSealTemplateField": {
        "type": "object",
        "required": [
          "key",
          "label",
          "type",
          "required"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          },
          "defaultValue": {
            "$ref": "#/components/schemas/MergeFieldPreviewValue"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "DocuSealTemplateResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "template",
              "fields"
            ],
            "properties": {
              "template": {
                "type": "object",
                "additionalProperties": true
              },
              "fields": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/DocuSealTemplateField"
                }
              },
              "submitterRoles": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "DocuSeal submitter role names in template order (two-party = staff first, client second)"
              },
              "teamTemplateFieldMap": {
                "type": "object",
                "description": "This team's saved merge targets for this template (DocuSeal field name → merge key or literal:text)",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "DocuSealCredentialsGetResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/DocuSealCredentialsStatus"
          }
        }
      },
      "DocuSealCredentialsPostBody": {
        "type": "object",
        "required": [
          "apiKey"
        ],
        "properties": {
          "apiKey": {
            "type": "string",
            "description": "DocuSeal API key"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Optional self-hosted DocuSeal URL"
          }
        }
      },
      "DocuSealCredentialsPatchBody": {
        "allOf": [
          {
            "type": "object",
            "description": "At least one of sendEmailViaApp or templateFieldMaps must be provided.",
            "properties": {
              "sendEmailViaApp": {
                "type": "boolean",
                "description": "Send signing invites via app email (Resend)"
              },
              "templateFieldMaps": {
                "type": "object",
                "description": "Partial update; merge by template id. Empty inner object removes that template's map.",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "sendEmailViaApp"
                ]
              },
              {
                "type": "object",
                "required": [
                  "templateFieldMaps"
                ]
              }
            ]
          }
        ]
      },
      "DocuSealCredentialsWriteData": {
        "type": "object",
        "description": "POST save or DELETE remove success payload; includes effective status after the mutation.",
        "required": [
          "success",
          "message",
          "configured",
          "credentialsOnThisTeam",
          "url",
          "enabledAt",
          "sendEmailViaApp"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "message": {
            "type": "string"
          },
          "configured": {
            "type": "boolean"
          },
          "credentialsOnThisTeam": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "enabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sendEmailViaApp": {
            "type": "boolean"
          }
        }
      },
      "DocuSealCredentialsWriteResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/DocuSealCredentialsWriteData"
          }
        }
      },
      "DocuSealCredentialsPatchResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "success",
              "sendEmailViaApp",
              "templateFieldMapsSaved"
            ],
            "properties": {
              "success": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "sendEmailViaApp": {
                "type": "boolean"
              },
              "templateFieldMapsSaved": {
                "type": "boolean",
                "description": "True when templateFieldMaps was in the request body"
              }
            }
          }
        }
      },
      "RequestSignatureBody": {
        "type": "object",
        "description": "POST body for document-based DocuSeal request (requestSignatureBodySchema).",
        "required": [
          "clientId",
          "signerEmail",
          "signerName"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "signerEmail": {
            "type": "string",
            "format": "email"
          },
          "signerName": {
            "type": "string",
            "minLength": 1
          },
          "templateId": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "sendEmail": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "DocumentSignatureRecord": {
        "type": "object",
        "description": "Created DocumentSignature row (subset of fields returned on success).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseDocumentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "externalId": {
            "type": "string",
            "nullable": true
          },
          "externalService": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "signerUrl": {
            "type": "string",
            "nullable": true,
            "description": "Primary client party signing URL from DocuSeal when a client-classified role exists (null when not applicable)"
          },
          "staffSignerUrl": {
            "type": "string",
            "nullable": true,
            "description": "Staff/practice party signing URL from DocuSeal for multi-party templates when a staff-classified role exists (null when not applicable)"
          },
          "templateName": {
            "type": "string",
            "nullable": true
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "DocuSealSignatureRequestSuccess": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "signature",
              "signingUrl",
              "staffSigningUrl",
              "submitterSigningUrls"
            ],
            "properties": {
              "signature": {
                "$ref": "#/components/schemas/DocumentSignatureRecord"
              },
              "signingUrl": {
                "type": "string",
                "nullable": true,
                "description": "Primary client party signing URL (first role classified as client) when provided by DocuSeal; null when not applicable"
              },
              "staffSigningUrl": {
                "type": "string",
                "nullable": true,
                "description": "Staff/practice party signing URL when a staff-classified role exists (multi-party templates); null when not applicable"
              },
              "submitterSigningUrls": {
                "type": "array",
                "nullable": true,
                "description": "All party signing URLs in template order (multi-party templates only)",
                "items": {
                  "type": "object",
                  "required": [
                    "index",
                    "role",
                    "url"
                  ],
                  "properties": {
                    "index": {
                      "type": "integer"
                    },
                    "role": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      },
      "IrReportRequest": {
        "type": "object",
        "description": "Create IR report. Multipart form-data. At least one of text or file required.",
        "required": [
          "irReportType"
        ],
        "anyOf": [
          {
            "type": "object",
            "required": [
              "text"
            ]
          },
          {
            "type": "object",
            "required": [
              "file"
            ]
          }
        ],
        "properties": {
          "irReportType": {
            "type": "string",
            "enum": [
              "client_phone_call",
              "prosecutor_phone_call",
              "other_phone_call",
              "closing_call",
              "intro_call",
              "info_only",
              "post_hearing",
              "case_ready_to_close",
              "discovery_check"
            ]
          },
          "text": {
            "type": "string",
            "description": "Text content (for text-only IR report)"
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "Audio (m4a, webm, mp3), document (PDF, Word), or image (JPEG, PNG, GIF, WebP)"
          }
        }
      },
      "Team": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TeamDetail": {
        "type": "object",
        "description": "Full team settings row returned by GET/PUT /api/teams/{id} (practice info, billing reminder toggles, template ids). Dates are ISO strings in JSON.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "emailSlug": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "practiceName": {
            "type": "string",
            "nullable": true
          },
          "practicePhone": {
            "type": "string",
            "nullable": true
          },
          "practiceEmail": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "practiceAddress1": {
            "type": "string",
            "nullable": true
          },
          "practiceAddress2": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string"
          },
          "sendPaymentReminderSms": {
            "type": "boolean"
          },
          "sendPaymentOverdueSms": {
            "type": "boolean"
          },
          "sendInvoiceReminderSms": {
            "type": "boolean"
          },
          "sendPaymentReminderEmail": {
            "type": "boolean"
          },
          "sendPaymentOverdueEmail": {
            "type": "boolean"
          },
          "sendInvoiceReminderEmail": {
            "type": "boolean"
          },
          "autoTimeTrackCommunications": {
            "type": "boolean",
            "description": "When true, log billable time for outbound SMS/MMS, completed client calls, and saved template-generated documents (per team increment, default 15 minutes)."
          },
          "autoTimeTrackIncrementMinutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440,
            "description": "Minutes per auto-logged activity (default 15; DB and API enforce 1–1440)."
          },
          "billingInvoiceSendEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingInvoiceReminderEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentPlanSendEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentReminderEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentOverdueEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingInvoiceReminderSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentReminderSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentOverdueSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TeamWithRole": {
        "type": "object",
        "description": "Reduced team fields plus role (e.g. switch response).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "role": {
            "type": "string"
          }
        }
      },
      "UpdateTeam": {
        "type": "object",
        "description": "Partial update; only provided fields are updated. Matches updateTeamSchema.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "nullable": true
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "nullable": true
          },
          "practiceName": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "practicePhone": {
            "type": "string",
            "maxLength": 50,
            "nullable": true
          },
          "practiceEmail": {
            "type": "string",
            "format": "email",
            "maxLength": 200,
            "nullable": true
          },
          "practiceAddress1": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "practiceAddress2": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "emailSlug": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "sendPaymentReminderSms": {
            "type": "boolean"
          },
          "sendPaymentOverdueSms": {
            "type": "boolean"
          },
          "sendInvoiceReminderSms": {
            "type": "boolean"
          },
          "sendPaymentReminderEmail": {
            "type": "boolean"
          },
          "sendPaymentOverdueEmail": {
            "type": "boolean"
          },
          "sendInvoiceReminderEmail": {
            "type": "boolean"
          },
          "autoTimeTrackCommunications": {
            "type": "boolean",
            "description": "When true, log billable time for outbound SMS/MMS, completed client calls, and saved template-generated documents (per team increment, default 15 minutes)."
          },
          "autoTimeTrackIncrementMinutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1440,
            "description": "Minutes per auto-logged activity (default 15; must be between 1 and 1440)."
          },
          "billingInvoiceSendEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingInvoiceReminderEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentPlanSendEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentReminderEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentOverdueEmailTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingInvoiceReminderSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentReminderSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "billingPaymentOverdueSmsTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "firmMergeFieldValues": {
            "type": "object",
            "description": "Per-team values for firm-defined merge keys (see Case field settings on the firm). Only keys defined on the billing root are stored.",
            "additionalProperties": {
              "type": "string",
              "maxLength": 5000
            }
          }
        }
      },
      "TeamMember": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "type": "string"
          },
          "User": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "username": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            }
          }
        }
      },
      "InviteToTeamsInviteResponse": {
        "type": "object",
        "description": "Response when invitation email is sent (new user or pending invite).",
        "required": [
          "invitations",
          "expiresInDays"
        ],
        "properties": {
          "invitations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "teamId"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "teamId": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          },
          "expiresInDays": {
            "type": "number"
          }
        }
      },
      "InviteToTeamsAutoAddResponse": {
        "type": "object",
        "description": "Response when user already had an account and was auto-added to team(s).",
        "required": [
          "autoAdded",
          "addedTeamMembers"
        ],
        "properties": {
          "autoAdded": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "addedTeamMembers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "teamId",
                "teamMemberId"
              ],
              "properties": {
                "teamId": {
                  "type": "string",
                  "format": "uuid"
                },
                "teamMemberId": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          }
        }
      },
      "InviteMemberInviteResponse": {
        "type": "object",
        "description": "Response when invitation email is sent.",
        "required": [
          "invitation"
        ],
        "properties": {
          "invitation": {
            "type": "object",
            "required": [
              "id",
              "email",
              "role",
              "expiresAt",
              "createdAt"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "InviteMemberAutoAddResponse": {
        "type": "object",
        "description": "Response when user already had an account and was auto-added.",
        "required": [
          "autoAdded",
          "teamMember"
        ],
        "properties": {
          "autoAdded": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "teamMember": {
            "type": "object",
            "required": [
              "id",
              "email",
              "role"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string"
              },
              "role": {
                "type": "string"
              }
            }
          }
        }
      },
      "AuditLog": {
        "type": "object",
        "description": "Single audit log entry (firm or team audit list).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "action": {
            "type": "string"
          },
          "entityType": {
            "type": "string",
            "nullable": true
          },
          "entityId": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "description": "Arbitrary JSON (object, array, or primitive) per sanitizeMetadata(); nullable.",
            "nullable": true
          },
          "ipAddress": {
            "type": "string",
            "nullable": true
          },
          "userAgent": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "errorMessage": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "User": {
            "nullable": true,
            "$ref": "#/components/schemas/User"
          },
          "Team": {
            "nullable": true,
            "$ref": "#/components/schemas/Team"
          }
        }
      },
      "PaginationMetadata": {
        "type": "object",
        "description": "Standardized pagination metadata for public list endpoints.",
        "required": [
          "limit",
          "offset",
          "total",
          "hasMore"
        ],
        "properties": {
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "hasMore": {
            "type": "boolean"
          }
        }
      },
      "ClientSearchRow": {
        "type": "object",
        "description": "Lightweight client row returned when `fields=search`.",
        "required": [
          "id",
          "firstName",
          "lastName",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          }
        }
      },
      "CaseSearchClientSummary": {
        "type": "object",
        "description": "Minimal client summary embedded in case search rows.",
        "required": [
          "id",
          "firstName",
          "lastName"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100
          },
          "lastName": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "CaseSearchRow": {
        "type": "object",
        "description": "Lightweight case row returned when `fields=search` or by global search.",
        "required": [
          "id",
          "title",
          "caseNumber",
          "Client"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "caseNumber": {
            "type": "string",
            "nullable": true
          },
          "Client": {
            "$ref": "#/components/schemas/CaseSearchClientSummary"
          }
        }
      },
      "ClientsListFullResponse": {
        "type": "object",
        "required": [
          "clients",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "clients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Client"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "ClientsListSearchResponse": {
        "type": "object",
        "required": [
          "clients",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "clients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientSearchRow"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "ClientsListResponse": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/ClientsListFullResponse"
          },
          {
            "$ref": "#/components/schemas/ClientsListSearchResponse"
          }
        ]
      },
      "CasesListFullResponse": {
        "type": "object",
        "required": [
          "cases",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "cases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Case"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "CasesListSearchResponse": {
        "type": "object",
        "required": [
          "cases",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "cases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CaseSearchRow"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "CasesListResponse": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/CasesListFullResponse"
          },
          {
            "$ref": "#/components/schemas/CasesListSearchResponse"
          }
        ]
      },
      "GlobalSearchClientsResponse": {
        "type": "object",
        "required": [
          "items",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientSearchRow"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "GlobalSearchCasesResponse": {
        "type": "object",
        "required": [
          "items",
          "pagination"
        ],
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CaseSearchRow"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "GlobalSearchResponse": {
        "type": "object",
        "required": [
          "clients",
          "cases"
        ],
        "additionalProperties": false,
        "properties": {
          "clients": {
            "$ref": "#/components/schemas/GlobalSearchClientsResponse"
          },
          "cases": {
            "$ref": "#/components/schemas/GlobalSearchCasesResponse"
          }
        }
      },
      "RequestSignatureFromTemplateBody": {
        "type": "object",
        "required": [
          "templateId",
          "clientId",
          "signerEmail",
          "signerName"
        ],
        "properties": {
          "templateId": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ]
          },
          "templateName": {
            "type": "string",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid"
          },
          "signerEmail": {
            "type": "string",
            "format": "email"
          },
          "signerName": {
            "type": "string"
          },
          "sendEmail": {
            "type": "boolean"
          },
          "submitterParties": {
            "type": "array",
            "description": "One object per DocuSeal template submitter in order (name, email, optional completeInApp). Required whenever the template has two or more submitters (no server-side two-party default; send explicit parties in DocuSeal submitter order).",
            "maxItems": 20,
            "items": {
              "type": "object",
              "required": [
                "email",
                "name"
              ],
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email"
                },
                "name": {
                  "type": "string"
                },
                "completeInApp": {
                  "type": "boolean",
                  "description": "When true, DocuSeal does not email this party (complete in app or share link manually)."
                }
              }
            }
          },
          "templateFieldValues": {
            "type": "object",
            "description": "Optional key/value map for sender-editable template fields populated before sending.",
            "additionalProperties": {
              "$ref": "#/components/schemas/MergeFieldPreviewValue"
            }
          }
        }
      },
      "InitiateCallBody": {
        "type": "object",
        "description": "Body for POST /api/twilio/calls (initiate outbound call).",
        "required": [
          "phoneNumberId",
          "toNumber"
        ],
        "properties": {
          "phoneNumberId": {
            "type": "string",
            "format": "uuid"
          },
          "toNumber": {
            "type": "string",
            "description": "E.164 or national format"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "record": {
            "type": "boolean",
            "default": true
          },
          "bridgeViaPhone": {
            "type": "boolean",
            "default": false
          },
          "userPhoneNumber": {
            "type": "string",
            "nullable": true,
            "description": "Required when bridgeViaPhone is true"
          }
        }
      },
      "ForwardCallBody": {
        "type": "object",
        "description": "Body for POST /api/twilio/calls/{callSid}/forward (forward active call to another number).",
        "required": [
          "toNumber"
        ],
        "properties": {
          "toNumber": {
            "type": "string",
            "description": "Destination number (E.164 or national format)"
          }
        }
      },
      "QuickTransferNumber": {
        "type": "object",
        "description": "A team quick-transfer number (label + E.164) for fast forward/transfer dial.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string",
            "maxLength": 80,
            "description": "Display label e.g. Reception"
          },
          "phoneNumber": {
            "type": "string",
            "description": "E.164 format"
          },
          "sortOrder": {
            "type": "integer",
            "description": "Display order"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Present in PATCH response"
          }
        }
      },
      "QuickTransferNumberCreateBody": {
        "type": "object",
        "description": "Body for POST /api/twilio/quick-transfer-numbers.",
        "required": [
          "label",
          "phoneNumber"
        ],
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "phoneNumber": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "E.164 or national format; normalized to E.164"
          },
          "sortOrder": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000,
            "description": "Optional display order"
          }
        }
      },
      "QuickTransferNumberUpdateBody": {
        "type": "object",
        "description": "Body for PATCH /api/twilio/quick-transfer-numbers/{id}. All fields optional.",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "phoneNumber": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "E.164 or national format"
          },
          "sortOrder": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000
          }
        }
      },
      "TwilioPhoneNumber": {
        "type": "object",
        "description": "Team phone number for calling/SMS.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "assignedTeamId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "assignedTeamName": {
            "type": "string",
            "nullable": true
          },
          "phoneNumber": {
            "type": "string"
          },
          "friendlyName": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string"
          },
          "capabilities": {
            "type": "object",
            "nullable": true
          },
          "isPrimary": {
            "type": "boolean"
          },
          "canSetAsPrimary": {
            "type": "boolean"
          },
          "forwardToNumber": {
            "type": "string",
            "nullable": true,
            "description": "E.164 forward destination when set"
          },
          "forwardDialTimeoutSeconds": {
            "type": "integer",
            "minimum": 5,
            "maximum": 600,
            "description": "Seconds to ring the call-forward number (TwiML Dial timeout)"
          },
          "enableVoicemail": {
            "type": "boolean"
          },
          "voicemailGreeting": {
            "type": "string",
            "nullable": true
          },
          "webhooksConfiguredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TwilioPhoneNumberUpdateBody": {
        "type": "object",
        "description": "Body for PUT /api/twilio/phone-numbers/{id}. All fields optional; send only fields to change.",
        "properties": {
          "isPrimary": {
            "type": "boolean",
            "description": "Set as primary for the owning team; requires webhooks configured"
          },
          "assignToTeamId": {
            "type": "string",
            "format": "uuid",
            "description": "Reassign this number to another firm team (or the firm); keeps the number in Twilio; clears primary unless isPrimary true is sent in the same request"
          },
          "friendlyName": {
            "type": "string",
            "maxLength": 255
          },
          "updateWebhooks": {
            "type": "boolean",
            "description": "When true, sets Twilio voice/SMS webhooks for this number to MyLawyerLink"
          },
          "forwardToNumber": {
            "type": "string",
            "nullable": true,
            "description": "E.164 destination for unanswered calls; null or omit to clear"
          },
          "forwardDialTimeoutSeconds": {
            "type": "integer",
            "minimum": 5,
            "maximum": 600,
            "description": "Seconds Twilio rings the forward number (TwiML Dial timeout)"
          },
          "enableVoicemail": {
            "type": "boolean"
          },
          "voicemailGreeting": {
            "type": "string",
            "maxLength": 500,
            "description": "Empty string clears custom greeting"
          }
        }
      },
      "PhoneNumbersListResponse": {
        "type": "object",
        "description": "GET /api/twilio/phone-numbers 200 response.",
        "properties": {
          "phoneNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TwilioPhoneNumber"
            }
          },
          "availableToAssign": {
            "type": "array",
            "description": "Numbers in Twilio account not yet on this team (can assign).",
            "items": {
              "$ref": "#/components/schemas/AvailableToAssignItem"
            }
          },
          "canManageNumbers": {
            "type": "boolean",
            "description": "True when caller can perform firm-level number mutations."
          },
          "billingTeamId": {
            "type": "string",
            "format": "uuid"
          },
          "assignableTeams": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "phoneNumbers",
          "availableToAssign"
        ]
      },
      "AvailableToAssignItem": {
        "type": "object",
        "description": "Number in account available to assign to this team.",
        "properties": {
          "sid": {
            "type": "string"
          },
          "phoneNumber": {
            "type": "string"
          },
          "friendlyName": {
            "type": "string",
            "nullable": true
          },
          "capabilities": {
            "type": "object"
          },
          "status": {
            "type": "string"
          },
          "assignedToTeamName": {
            "type": "string",
            "description": "Present when assigned to another team"
          }
        }
      },
      "PhoneNumberSearchRequest": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/PhoneNumberSearchRequest_search"
          },
          {
            "$ref": "#/components/schemas/PhoneNumberSearchRequest_purchase"
          },
          {
            "$ref": "#/components/schemas/PhoneNumberSearchRequest_assign"
          }
        ],
        "discriminator": {
          "propertyName": "action"
        },
        "description": "Body for POST /api/twilio/phone-numbers (search, purchase, or assign). Mirrors phoneNumberRequestSchema."
      },
      "PhoneNumberSearchRequest_search": {
        "type": "object",
        "description": "Variant when action=search.",
        "required": [
          "action"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "search"
            ]
          },
          "phoneNumber": {
            "type": "string"
          },
          "areaCode": {
            "type": "string"
          },
          "countryCode": {
            "type": "string"
          }
        }
      },
      "PhoneNumberSearchRequest_purchase": {
        "type": "object",
        "description": "Variant when action=purchase; phoneNumber required.",
        "required": [
          "action",
          "phoneNumber"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "purchase"
            ]
          },
          "phoneNumber": {
            "type": "string"
          },
          "targetTeamId": {
            "type": "string",
            "format": "uuid"
          },
          "areaCode": {
            "type": "string"
          },
          "countryCode": {
            "type": "string"
          }
        }
      },
      "PhoneNumberSearchRequest_assign": {
        "type": "object",
        "description": "Variant when action=assign; assign existing Twilio number to this team.",
        "required": [
          "action",
          "twilioSid"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "assign"
            ]
          },
          "twilioSid": {
            "type": "string",
            "description": "Twilio SID of the number to assign"
          },
          "targetTeamId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "AvailableNumberItem": {
        "type": "object",
        "description": "Single available number from search.",
        "properties": {
          "phoneNumber": {
            "type": "string"
          },
          "friendlyName": {
            "type": "string",
            "nullable": true
          },
          "locality": {
            "type": "string",
            "nullable": true
          },
          "region": {
            "type": "string",
            "nullable": true
          },
          "capabilities": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "AvailableNumbersResponse": {
        "type": "object",
        "description": "Response when action=search.",
        "properties": {
          "availableNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailableNumberItem"
            }
          }
        }
      },
      "PhoneNumberPurchaseResponse": {
        "type": "object",
        "description": "Response when action=purchase succeeds.",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "phoneNumber": {
            "$ref": "#/components/schemas/TwilioPhoneNumber"
          }
        }
      },
      "CallSummary": {
        "type": "object",
        "description": "Call record returned by POST /api/twilio/calls.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "twilioCallSid": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "toNumber": {
            "type": "string"
          },
          "fromNumber": {
            "type": "string"
          },
          "direction": {
            "type": "string"
          },
          "phoneNumberId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "CallDetail": {
        "type": "object",
        "description": "Full call details from GET /api/twilio/calls/{id}.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "phoneNumberId": {
            "type": "string",
            "format": "uuid"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "twilioCallSid": {
            "type": "string"
          },
          "direction": {
            "type": "string"
          },
          "fromNumber": {
            "type": "string"
          },
          "toNumber": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "duration": {
            "type": "integer",
            "nullable": true
          },
          "startTime": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "record": {
            "type": "boolean"
          },
          "transferStatus": {
            "type": "string",
            "nullable": true,
            "enum": [
              "holding",
              "consulting",
              "completed",
              "starting",
              "dialing",
              "cancelling",
              "completing"
            ],
            "description": "Call transfer status; null when not in transfer."
          },
          "incomingRingSeconds": {
            "type": "integer",
            "minimum": 5,
            "maximum": 600,
            "description": "Browser/PSTN ring timeout in seconds for this line (from team phone settings)."
          },
          "hasCallForward": {
            "type": "boolean",
            "description": "True when the line has a configured forward-to number."
          },
          "recording": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "CallHistoryItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "phoneNumberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "caseId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "twilioCallSid": {
            "type": "string",
            "nullable": true
          },
          "direction": {
            "type": "string"
          },
          "fromNumber": {
            "type": "string"
          },
          "toNumber": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "duration": {
            "type": "integer",
            "nullable": true
          },
          "record": {
            "type": "boolean"
          },
          "startTime": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "endTime": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "client": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "firstName": {
                "type": "string",
                "nullable": true
              },
              "lastName": {
                "type": "string",
                "nullable": true
              },
              "name": {
                "type": "string",
                "nullable": true
              },
              "phone": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "case": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "title": {
                "type": "string"
              },
              "caseNumber": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "phoneNumber": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "phoneNumber": {
                "type": "string"
              },
              "friendlyName": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "initiatedByUser": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "username": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "answeredByUser": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "username": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "recording": {
            "$ref": "#/components/schemas/CallRecordingSummary",
            "nullable": true
          }
        }
      },
      "CallsListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "calls": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CallHistoryItem"
                }
              },
              "pagination": {
                "$ref": "#/components/schemas/PaginationMetadata"
              }
            }
          }
        }
      },
      "VoicemailItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "call": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "direction": {
                "type": "string"
              },
              "fromNumber": {
                "type": "string"
              },
              "toNumber": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "duration": {
                "type": "integer",
                "nullable": true
              },
              "startTime": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "client": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "case": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string"
                  },
                  "caseNumber": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "recording": {
            "$ref": "#/components/schemas/CallRecordingSummary"
          }
        }
      },
      "VoicemailsListResponse": {
        "type": "object",
        "properties": {
          "voicemails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VoicemailItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/PaginationMetadata"
          }
        }
      },
      "MmsTemplateMediaUrl": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048,
        "description": "HTTPS team-scoped template-attachment URL (sms or email path under /teams/{uuid}/template-attachments/); mirrors MESSAGE_TEMPLATE_ATTACHMENT_URL_JSON_SCHEMA_PATTERN / server validation.",
        "pattern": "^https://[^/]+/teams/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/template-attachments/(sms|email)(?:/.*)?$"
      },
      "SendMessageBody": {
        "type": "object",
        "description": "Body for POST /api/twilio/messages (send SMS or MMS). Either a non-empty body or at least one MMS URL is required.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "toNumber"
            ],
            "properties": {
              "teamId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "phoneNumberId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Ignored; from number is team primary"
              },
              "toNumber": {
                "type": "string",
                "description": "E.164-capable destination; normalized server-side"
              },
              "body": {
                "type": "string"
              },
              "mediaUrls": {
                "type": "array",
                "maxItems": 10,
                "items": {
                  "$ref": "#/components/schemas/MmsTemplateMediaUrl"
                }
              },
              "clientId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              }
            }
          },
          {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "body"
                ],
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Non-empty text (whitespace-only rejected at runtime)"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "mediaUrls"
                ],
                "properties": {
                  "mediaUrls": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/MmsTemplateMediaUrl"
                    }
                  }
                }
              }
            ]
          }
        ]
      },
      "SendMessagePostMessage": {
        "type": "object",
        "description": "Outbound message row returned after POST /api/twilio/messages.",
        "required": [
          "id",
          "body",
          "mediaUrls",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "body": {
            "type": "string"
          },
          "mediaUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "status": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SendMessageResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "success",
              "message"
            ],
            "properties": {
              "success": {
                "type": "boolean"
              },
              "message": {
                "$ref": "#/components/schemas/SendMessagePostMessage"
              }
            }
          }
        }
      },
      "MessageItem": {
        "type": "object",
        "description": "Single message in a conversation.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "fromNumber": {
            "type": "string"
          },
          "toNumber": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "mediaUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "MMS URLs (Twilio API URLs for inbound; blob URLs for outbound template MMS)"
          },
          "status": {
            "type": "string",
            "nullable": true
          },
          "sentByUsername": {
            "type": "string",
            "nullable": true,
            "description": "Staff username for outbound sends"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SaveTwilioMessageMediaToCaseRequest": {
        "type": "object",
        "required": [
          "caseId"
        ],
        "properties": {
          "caseId": {
            "type": "string",
            "format": "uuid",
            "description": "Case to attach the saved MMS image to"
          }
        }
      },
      "TwilioTokenResponse": {
        "type": "object",
        "description": "Response from GET /api/twilio/token (browser Voice SDK).",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "accessToken": {
                "type": "string"
              },
              "phoneNumbers": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "teamId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "teamName": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "TwilioTeamTokenItem": {
        "type": "object",
        "description": "One team's Twilio access token and phone numbers (for multi-team Voice SDK).",
        "required": [
          "teamId",
          "teamName",
          "accessToken",
          "phoneNumbers",
          "voicePushCredentialConfigured"
        ],
        "properties": {
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "teamName": {
            "type": "string"
          },
          "accessToken": {
            "type": "string"
          },
          "phoneNumbers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "phoneNumber": {
                  "type": "string"
                }
              }
            }
          },
          "voicePushCredentialConfigured": {
            "type": "boolean",
            "description": "When voicePlatform was omitted, true. When android/ios was requested, true only if pushCredentialSid was included in the token grant (team DB or env fallback)."
          }
        }
      },
      "TwilioVoiceFcmAutoProvisionCapability": {
        "type": "object",
        "required": [
          "enabled",
          "serverConfigured",
          "available"
        ],
        "description": "Whether POST /voice-push/provision can run (feature flag + server Firebase JSON).",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "TWILIO_VOICE_FCM_AUTO_PROVISION_ENABLED"
          },
          "serverConfigured": {
            "type": "boolean",
            "description": "Valid Firebase service account JSON in server env (BASE64 or raw)"
          },
          "available": {
            "type": "boolean",
            "description": "enabled and serverConfigured"
          }
        }
      },
      "TwilioVoicePushCredentialsData": {
        "type": "object",
        "required": [
          "twilioVoiceFcmPushCredentialSid",
          "twilioVoiceApnsPushCredentialSid",
          "fcmAutoProvision",
          "fcmPushCredentialFromEnv",
          "apnsPushCredentialFromEnv"
        ],
        "properties": {
          "twilioVoiceFcmPushCredentialSid": {
            "type": "string",
            "nullable": true,
            "description": "Twilio Push Credential SID (CR…) for Android FCM Voice notify"
          },
          "twilioVoiceApnsPushCredentialSid": {
            "type": "string",
            "nullable": true,
            "description": "Twilio Push Credential SID (CR…) for iOS VoIP (optional)"
          },
          "fcmAutoProvision": {
            "$ref": "#/components/schemas/TwilioVoiceFcmAutoProvisionCapability"
          },
          "fcmPushCredentialFromEnv": {
            "type": "boolean",
            "description": "True when TWILIO_VOICE_FCM_PUSH_CREDENTIAL_SID is set (fallback if team row has no FCM CR…)"
          },
          "apnsPushCredentialFromEnv": {
            "type": "boolean",
            "description": "True when TWILIO_VOICE_APNS_PUSH_CREDENTIAL_SID is set (fallback if team row has no APNs CR…)"
          }
        }
      },
      "ProvisionTwilioFcmVoicePushRequest": {
        "type": "object",
        "properties": {
          "force": {
            "type": "boolean",
            "description": "Create a new Twilio credential even if a SID is already stored"
          }
        }
      },
      "ProvisionTwilioFcmVoicePushResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "twilioVoiceFcmPushCredentialSid",
              "alreadyProvisioned"
            ],
            "properties": {
              "twilioVoiceFcmPushCredentialSid": {
                "type": "string",
                "description": "Twilio CR… (existing or newly created)"
              },
              "alreadyProvisioned": {
                "type": "boolean",
                "description": "True when SID was already stored and force was not used"
              },
              "previousCredentialRemovedFromTwilio": {
                "type": "boolean",
                "description": "When present and true, force rotation removed the prior CR… from Twilio Comms"
              }
            }
          }
        }
      },
      "SyncTwilioVoicePushNotifyRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "overwriteFcm": {
            "type": "boolean",
            "description": "When true, replace stored FCM CR… with the newest FCM or GCM credential from Notify list"
          },
          "overwriteApns": {
            "type": "boolean",
            "description": "When true, replace stored APNs CR… with the newest apn credential from Notify list"
          }
        }
      },
      "SyncTwilioVoicePushNotifyResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TwilioVoicePushCredentialsData"
              },
              {
                "type": "object",
                "required": [
                  "updatedFcm",
                  "updatedApns",
                  "discoveredFcmSid",
                  "discoveredApnsSid",
                  "notifyCredentialsListed"
                ],
                "properties": {
                  "updatedFcm": {
                    "type": "boolean",
                    "description": "Whether the FCM/GCM notify credential SID was updated"
                  },
                  "updatedApns": {
                    "type": "boolean",
                    "description": "Whether the APNs notify credential SID was updated"
                  },
                  "discoveredFcmSid": {
                    "type": "string",
                    "nullable": true,
                    "description": "Newest FCM or GCM Notify credential SID found (null if none)"
                  },
                  "discoveredApnsSid": {
                    "type": "string",
                    "nullable": true,
                    "description": "Newest APNs Notify credential SID found (null if none)"
                  },
                  "notifyCredentialsListed": {
                    "type": "integer",
                    "description": "Total credentials retrieved from the Twilio Notify API"
                  }
                }
              }
            ]
          }
        }
      },
      "TwilioVoicePushCredentialsResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/TwilioVoicePushCredentialsData"
          }
        }
      },
      "PatchTwilioVoicePushCredentialsRequest": {
        "type": "object",
        "minProperties": 1,
        "description": "At least one property required; use null to clear a stored SID.",
        "properties": {
          "twilioVoiceFcmPushCredentialSid": {
            "type": "string",
            "nullable": true,
            "pattern": "^CR[0-9a-fA-F]{32}$"
          },
          "twilioVoiceApnsPushCredentialSid": {
            "type": "string",
            "nullable": true,
            "pattern": "^CR[0-9a-fA-F]{32}$"
          }
        }
      },
      "TwilioTokensResponse": {
        "type": "object",
        "description": "Response from GET /api/twilio/tokens (multi-team Voice SDK; e.g. mobile inbound).",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "tokens",
              "tokenEligibility"
            ],
            "properties": {
              "tokens": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TwilioTeamTokenItem"
                }
              },
              "tokenEligibility": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/TeamTokenEligibilityDiagnostic"
                }
              }
            }
          }
        }
      },
      "TokenEligibilityStatus": {
        "type": "string",
        "enum": [
          "token_issued",
          "skipped"
        ]
      },
      "TokenSkipReasonCode": {
        "type": "string",
        "enum": [
          "TWILIO_NOT_CONFIGURED",
          "TWILIO_API_KEY_MISSING",
          "NO_ACTIVE_PHONE_NUMBERS",
          "TWIML_APP_UNAVAILABLE"
        ]
      },
      "TokenWarningCode": {
        "type": "string",
        "enum": [
          "VOICE_PUSH_CREDENTIAL_MISSING"
        ]
      },
      "TeamTokenEligibilityDiagnostic": {
        "type": "object",
        "required": [
          "teamId",
          "teamName",
          "status",
          "reasonCode",
          "reason",
          "warningCodes",
          "voicePushCredentialConfigured",
          "hasTwilioCredentials",
          "hasApiKey",
          "activePhoneNumbersCount",
          "phoneNumbersChecked",
          "hasTwimlAppSid"
        ],
        "properties": {
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "teamName": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TokenEligibilityStatus"
          },
          "reasonCode": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TokenSkipReasonCode"
              }
            ],
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "warningCodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TokenWarningCode"
            }
          },
          "voicePushCredentialConfigured": {
            "type": "boolean"
          },
          "hasTwilioCredentials": {
            "type": "boolean"
          },
          "hasApiKey": {
            "type": "boolean"
          },
          "activePhoneNumbersCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Count of active phone numbers when phoneNumbersChecked is true."
          },
          "phoneNumbersChecked": {
            "type": "boolean",
            "description": "False when eligibility exited before phone-number lookup (do not interpret activePhoneNumbersCount as evaluated)."
          },
          "hasTwimlAppSid": {
            "type": "boolean"
          }
        }
      },
      "TwilioCallAnsweringAvailabilityResponse": {
        "type": "object",
        "description": "Response from GET /api/twilio/call-answering-available.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "twilioDeviceAvailable",
              "available",
              "browserNotificationBehavior"
            ],
            "properties": {
              "twilioDeviceAvailable": {
                "type": "boolean",
                "description": "True when at least one active membership can initialize Twilio Voice Device (subscription + credentials + active phone number)."
              },
              "available": {
                "type": "boolean",
                "description": "Backward-compatible alias of twilioDeviceAvailable."
              },
              "browserNotificationBehavior": {
                "type": "string",
                "description": "Clarifies that incoming-call browser notifications are SSE-driven and not gated by twilioDeviceAvailable."
              }
            }
          }
        }
      },
      "TaskAutomationRule": {
        "type": "object",
        "description": "Task automation rule (create tasks from triggers). triggerType determines when the rule runs.\nServer validation (superRefine, not a discriminated union) enforces conditionals: when triggerType is case_event_created, triggerEventTypes must contain at least one value; when triggerType is case_status_change, triggerCaseStatuses must contain at least one value; case_custom_field_change, client_custom_field_change, and case_event_custom_field_change require at least one triggerCustomFieldNames entry (optional triggerCustomFieldTargetValues); client_created and client_assigned_to_team need no extra trigger fields; billing cron triggers require triggerDaysAfterDue with trigger-specific ranges (see triggerDaysAfterDue). GET/POST/PUT include assignee when present. triggerCustomFieldNames and triggerCustomFieldTargetValues are always string arrays in JSON (never null), matching Zod defaults and API normalization.\n",
        "required": [
          "id",
          "teamId",
          "triggerType",
          "triggerEventTypes",
          "triggerCustomFieldNames",
          "triggerCustomFieldTargetValues",
          "daysOffset",
          "taskTitle",
          "enabled",
          "triggerDaysAfterDue",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "triggerType": {
            "type": "string",
            "enum": [
              "case_event_created",
              "case_status_change",
              "case_custom_field_change",
              "client_custom_field_change",
              "case_event_custom_field_change",
              "client_created",
              "client_assigned_to_team",
              "invoice_overdue",
              "payment_plan_installment_overdue",
              "invoice_due_relative_before",
              "invoice_due_relative_after",
              "payment_plan_installment_due_relative_before",
              "payment_plan_installment_due_relative_after"
            ],
            "description": "When to run the rule. case_event_created = when a case event of selected types is created; case_status_change = when case status changes to selected statuses; case_custom_field_change / client_custom_field_change / case_event_custom_field_change = when configured custom fields change on case, client, or case event (optional target values); client_created = task on client first case; client_assigned_to_team = task on client first case in this team; invoice_* / payment_plan_installment_* billing triggers = daily billing cron (deduped per invoice or installment) using triggerDaysAfterDue per trigger semantics."
          },
          "triggerEventTypes": {
            "type": "array",
            "minItems": 0,
            "description": "For triggerType case_event_created, at least one required. Ignored for other types.",
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          },
          "triggerCaseStatuses": {
            "type": "array",
            "nullable": true,
            "description": "For triggerType case_status_change, at least one required. Values must be firm-configured case statuses. Ignored for other types.",
            "items": {
              "type": "string",
              "maxLength": 64
            }
          },
          "triggerCustomFieldNames": {
            "type": "array",
            "description": "String array (empty when unused). For case_custom_field_change, client_custom_field_change, and case_event_custom_field_change at least one entry required on create/update (Zod). API responses always return an array of strings (never null). Server trims each item; blank items are invalid.",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          "triggerCustomFieldTargetValues": {
            "type": "array",
            "description": "String array (empty when unused; never null in JSON). When non-empty on create/update, rule runs only if a watched field's new value matches one of these strings after normalization. Each item max 500 characters (Zod).",
            "items": {
              "type": "string",
              "maxLength": 500
            }
          },
          "daysOffset": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365
          },
          "triggerDaysAfterDue": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365,
            "nullable": true,
            "description": "Billing cron only. invoice_overdue / payment_plan_installment_overdue: 0–365 = whole calendar days after due before creating the task (0 = first full day after due). invoice_due_relative_before / payment_plan_installment_due_relative_before: -365..-1 = that many calendar days before due. invoice_due_relative_after / payment_plan_installment_due_relative_after: 0–365 = that many calendar days on or after due (0 = due date). Team timezone. Null when unused.\n"
          },
          "taskTitle": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "taskDescription": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "assignToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "assignToTeamMember": {
            "type": "object",
            "description": "Present in GET/POST/PUT when assignToTeamMemberId is set.",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "User": {
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "enabled": {
            "type": "boolean"
          },
          "usePreviousFridayIfWeekendOrMonday": {
            "type": "boolean",
            "description": "When true",
            "if the task due date would fall on Saturday": null,
            "Sunday": null,
            "or Monday in the case team's IANA timezone (not UTC)": null,
            "it is set to the previous Friday in that local calendar sense.": null
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TaskAutomationRuleUpdateRequest": {
        "type": "object",
        "description": "Partial update body for PUT /api/teams/{id}/task-automation-rules/{ruleId}. All properties optional. The server merges the body with the existing rule for validation only; only keys present in the request JSON are written to the database (plus derived clears when triggerType changes away from custom-field or billing cron triggers).",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "triggerType": {
            "type": "string",
            "enum": [
              "case_event_created",
              "case_status_change",
              "case_custom_field_change",
              "client_custom_field_change",
              "case_event_custom_field_change",
              "client_created",
              "client_assigned_to_team",
              "invoice_overdue",
              "payment_plan_installment_overdue",
              "invoice_due_relative_before",
              "invoice_due_relative_after",
              "payment_plan_installment_due_relative_before",
              "payment_plan_installment_due_relative_after"
            ]
          },
          "triggerEventTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          },
          "triggerCaseStatuses": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string",
              "maxLength": 64
            }
          },
          "triggerCustomFieldNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "triggerCustomFieldTargetValues": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "triggerDaysAfterDue": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365,
            "nullable": true,
            "description": "Meaning depends on triggerType (server Zod superRefine). Omit or null when triggerType is not a billing cron trigger.\ninvoice_overdue / payment_plan_installment_overdue: non-negative only — whole calendar days to wait after the due date in the team timezone (0 = first full local day after due). Zero does not mean “on due date”.\ninvoice_due_relative_before / payment_plan_installment_due_relative_before: negative only (e.g. -7) — that many calendar days before due.\ninvoice_due_relative_after / payment_plan_installment_due_relative_after: non-negative — 0 = on the due date, 1 = one local day after, etc.\n"
          },
          "daysOffset": {
            "type": "integer",
            "minimum": -365,
            "maximum": 365
          },
          "taskTitle": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "taskDescription": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true
          },
          "assignToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "enabled": {
            "type": "boolean"
          },
          "usePreviousFridayIfWeekendOrMonday": {
            "type": "boolean",
            "description": "When true",
            "if the task due date would fall on Saturday": null,
            "Sunday": null,
            "or Monday in the case team's IANA timezone (not UTC)": null,
            "it is set to the previous Friday in that local sense.": null
          }
        }
      },
      "AiKnowledgeAutomationRule": {
        "type": "object",
        "description": "AI knowledge digest rule: aggregates calls, SMS, email, IR reports, and calendar data into a Knowledge wiki page using a team prompt. Runs manually or daily at scheduleHourLocal:scheduleMinuteLocal in Team.timezone. Requires a run-as team member with knowledge.edit. schedule DAILY requires scheduleHourLocal 0–23.\n",
        "required": [
          "id",
          "teamId",
          "name",
          "enabled",
          "instructionsPrompt",
          "timeWindowHours",
          "schedule",
          "scheduleHourLocal",
          "scheduleMinuteLocal",
          "outputTitleTemplate",
          "outputParentPageId",
          "outputVisibility",
          "outputTags",
          "runAsTeamMemberId",
          "runAsTeamMember",
          "lastRunAt",
          "lastRunStatus",
          "lastRunError",
          "lastOutputPageId",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "enabled": {
            "type": "boolean"
          },
          "instructionsPrompt": {
            "type": "string",
            "maxLength": 16000
          },
          "timeWindowHours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 168
          },
          "schedule": {
            "type": "string",
            "enum": [
              "MANUAL_ONLY",
              "DAILY"
            ]
          },
          "scheduleHourLocal": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23,
            "nullable": true
          },
          "scheduleMinuteLocal": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59,
            "description": "Used with DAILY schedule in team timezone."
          },
          "outputTitleTemplate": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Supports {{date}} replaced with the run date in team timezone."
          },
          "outputParentPageId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "outputVisibility": {
            "type": "string",
            "enum": [
              "TEAM",
              "FIRM"
            ]
          },
          "outputTags": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "runAsTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "runAsTeamMember": {
            "type": "object",
            "nullable": true,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "user": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "username": {
                    "type": "string",
                    "nullable": true
                  },
                  "email": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          },
          "lastRunAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastRunStatus": {
            "type": "string",
            "nullable": true
          },
          "lastRunError": {
            "type": "string",
            "nullable": true
          },
          "lastOutputPageId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AiKnowledgeAutomationRuleUpdateRequest": {
        "type": "object",
        "description": "Partial update for PUT /api/teams/{id}/ai-knowledge-automation-rules/{ruleId}. When sent, `name` and `instructionsPrompt` must be non-empty after trim (mirrors Zod `.trim().min(1)`).\nThe server merges with the stored row, then validates the merged schedule: if `schedule` is `DAILY`, `scheduleHourLocal` (0-23) must be set on the merged rule (HTTP 400 otherwise).\n",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "enabled": {
            "type": "boolean"
          },
          "instructionsPrompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000
          },
          "timeWindowHours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 168
          },
          "schedule": {
            "type": "string",
            "enum": [
              "MANUAL_ONLY",
              "DAILY"
            ]
          },
          "scheduleHourLocal": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23,
            "nullable": true
          },
          "scheduleMinuteLocal": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59
          },
          "outputTitleTemplate": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "outputParentPageId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "outputVisibility": {
            "type": "string",
            "enum": [
              "TEAM",
              "FIRM"
            ]
          },
          "outputTags": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "runAsTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "AiKnowledgeAutomationRuleCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "instructionsPrompt",
          "runAsTeamMemberId"
        ],
        "description": "Mirrors aiKnowledgeAutomationRuleCreateBodySchema. `name` and `instructionsPrompt` are trimmed and must be non-empty.\nIf `schedule` is `DAILY`, `scheduleHourLocal` (0-23) is required in the same payload. Omitting `schedule` defaults to MANUAL_ONLY on the server.\n",
        "allOf": [
          {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "enabled": {
                "type": "boolean",
                "default": true
              },
              "instructionsPrompt": {
                "type": "string",
                "minLength": 1,
                "maxLength": 16000
              },
              "timeWindowHours": {
                "type": "integer",
                "minimum": 1,
                "maximum": 168,
                "default": 24
              },
              "schedule": {
                "type": "string",
                "enum": [
                  "MANUAL_ONLY",
                  "DAILY"
                ],
                "default": "MANUAL_ONLY"
              },
              "scheduleHourLocal": {
                "type": "integer",
                "minimum": 0,
                "maximum": 23,
                "nullable": true
              },
              "scheduleMinuteLocal": {
                "type": "integer",
                "minimum": 0,
                "maximum": 59,
                "default": 0
              },
              "outputTitleTemplate": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500,
                "default": "AI digest {{date}}"
              },
              "outputParentPageId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "outputVisibility": {
                "type": "string",
                "enum": [
                  "TEAM",
                  "FIRM"
                ],
                "default": "TEAM"
              },
              "outputTags": {
                "type": "array",
                "default": [],
                "maxItems": 30,
                "items": {
                  "type": "string",
                  "maxLength": 80
                }
              },
              "runAsTeamMemberId": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "anyOf": [
              {
                "type": "object",
                "required": [
                  "schedule",
                  "scheduleHourLocal"
                ],
                "properties": {
                  "schedule": {
                    "type": "string",
                    "enum": [
                      "DAILY"
                    ]
                  },
                  "scheduleHourLocal": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 23
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "schedule": {
                    "type": "string",
                    "enum": [
                      "MANUAL_ONLY"
                    ]
                  }
                }
              },
              {
                "not": {
                  "required": [
                    "schedule"
                  ]
                }
              }
            ]
          }
        ]
      },
      "AiKnowledgeAutomationPreviewResponse": {
        "type": "object",
        "description": "POST /api/teams/{id}/ai-knowledge-automation-rules/{ruleId}/preview success body.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "markdown",
              "titlePreview"
            ],
            "properties": {
              "markdown": {
                "type": "string",
                "description": "Generated digest markdown (no wiki page created)"
              },
              "titlePreview": {
                "type": "string",
                "description": "Resolved title from the rule template for the run date"
              }
            }
          }
        }
      },
      "AiKnowledgeAutomationWritableParentPagesResponse": {
        "type": "object",
        "description": "GET writable wiki parent folder options for a chosen run-as member (AI digest rules UI). Omit limit and offset to return the full sorted list; otherwise results are windowed. Use q to search titles.",
        "required": [
          "pages",
          "hasMore"
        ],
        "properties": {
          "pages": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "title",
                "pathLabel"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "title": {
                  "type": "string"
                },
                "pathLabel": {
                  "type": "string",
                  "description": "Full wiki path from root to this page (parent / child / title) for hierarchy display"
                }
              }
            }
          },
          "limit": {
            "type": "integer",
            "minimum": 0,
            "description": "Echoed window size or full row count when unpaginated (can exceed 200); 0 when there are no rows"
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 50000,
            "description": "Skip N rows when paginating; defaults to 0 when limit is set"
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when additional rows exist after this window"
          }
        }
      },
      "CalendarFeed": {
        "type": "object",
        "description": "Named calendar feed (Events & Calendar). Token is used in feed URL.",
        "required": [
          "id",
          "name",
          "token",
          "eventTypes",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "token": {
            "type": "string",
            "description": "Secret token for feed URL GET /api/calendar/feed/userId/token"
          },
          "eventTypes": {
            "type": "array",
            "minItems": 0,
            "description": "Event type filter; empty = all events",
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarFeedInput": {
        "type": "object",
        "description": "Create calendar feed body. Mirrors createFeedSchema. Empty eventTypes = all events.",
        "required": [
          "name",
          "eventTypes"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "eventTypes": {
            "type": "array",
            "minItems": 0,
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          }
        }
      },
      "CalendarFeedUpdate": {
        "type": "object",
        "description": "Update calendar feed body (partial). Mirrors updateFeedSchema. Empty eventTypes = all events.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "eventTypes": {
            "type": "array",
            "minItems": 0,
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          }
        }
      },
      "CalendarFeedListItem": {
        "type": "object",
        "description": "Calendar feed metadata in list (no token). Use GET /api/calendar/feeds/{id}/token to reveal token for URL.",
        "required": [
          "id",
          "name",
          "eventTypes",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "eventTypes": {
            "type": "array",
            "minItems": 0,
            "items": {
              "type": "string",
              "enum": [
                "court_date",
                "deadline",
                "meeting",
                "communication",
                "filing",
                "document",
                "billing",
                "task",
                "drive_time",
                "trial",
                "other"
              ]
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarFeedsListResponse": {
        "type": "object",
        "properties": {
          "feeds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalendarFeedListItem"
            }
          }
        },
        "required": [
          "feeds"
        ]
      },
      "CalendarFeedResponse": {
        "type": "object",
        "properties": {
          "feed": {
            "$ref": "#/components/schemas/CalendarFeed"
          }
        },
        "required": [
          "feed"
        ]
      },
      "CalendarFeedTokenResponse": {
        "type": "object",
        "description": "Token and owner userId for building the calendar feed subscribe URL.",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "Secret token for GET /api/calendar/feed/{userId}/{token}"
          },
          "userId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Feed owner user id for the URL; null for legacy feeds"
          }
        }
      },
      "FirmCourtReportMatch": {
        "type": "object",
        "description": "A row from court filing reports matched by case number (firm intake helper).",
        "required": [
          "id",
          "first",
          "last",
          "case_number",
          "filing_date",
          "type",
          "charges"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "first": {
            "type": "string"
          },
          "last": {
            "type": "string"
          },
          "case_number": {
            "type": "integer"
          },
          "filing_date": {
            "type": "string",
            "format": "date-time"
          },
          "type": {
            "type": "string"
          },
          "county": {
            "type": "string",
            "nullable": true
          },
          "court": {
            "type": "string",
            "nullable": true
          },
          "charges": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "FirmClientAssignTeamEventOverride": {
        "type": "object",
        "description": "Optional per-event overrides after case transfer (destination team member and SMS/email template ids must belong to the destination team)",
        "required": [
          "eventId"
        ],
        "properties": {
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "assignedToTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "smsOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "smsReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "smsOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "smsOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnCreateTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailReminderTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDateTimeChangeTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "emailOnDeleteTemplateId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "FirmClientAssignTeamRequest": {
        "type": "object",
        "description": "Body for POST /api/firm/clients/{id}/assign-team",
        "required": [
          "teamId"
        ],
        "properties": {
          "teamId": {
            "type": "string",
            "format": "uuid",
            "description": "Child team to assign the client to"
          },
          "eventOverrides": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/FirmClientAssignTeamEventOverride"
            }
          }
        }
      },
      "FirmClientUnassignTeamRequestBody": {
        "type": "object",
        "description": "Optional JSON body for DELETE /api/firm/clients/{id}/assign-team (same eventOverrides shape as assign)",
        "properties": {
          "eventOverrides": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/FirmClientAssignTeamEventOverride"
            }
          }
        }
      },
      "FirmClientAssignTeamResponse": {
        "type": "object",
        "description": "Response when client is assigned to a child team",
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "assigned",
              "teamId"
            ],
            "properties": {
              "assigned": {
                "type": "boolean"
              },
              "teamId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      },
      "FirmClientUnassignTeamResponse": {
        "type": "object",
        "description": "Response when client is unassigned from a child team",
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "unassigned",
              "teamId"
            ],
            "properties": {
              "unassigned": {
                "type": "boolean"
              },
              "teamId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      },
      "FirmClientAssignTeamPreviewResponse": {
        "type": "object",
        "description": "Response for GET assign-team with preview=1 (calendar event / SMS+email link impact). With includeChoices=1, also returns eventBreakdown, destinationTeamMembers, destinationSmsTemplates, destinationEmailTemplates.",
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "caseCount",
              "eventCount",
              "assigneesRemapped",
              "assigneesCleared",
              "smsSlotsRemapped",
              "smsSlotsCleared",
              "emailSlotsRemapped",
              "emailSlotsCleared",
              "lostSmsTemplateNames",
              "lostEmailTemplateNames",
              "isClean"
            ],
            "properties": {
              "caseCount": {
                "type": "integer",
                "minimum": 0
              },
              "eventCount": {
                "type": "integer",
                "minimum": 0
              },
              "assigneesRemapped": {
                "type": "integer",
                "minimum": 0
              },
              "assigneesCleared": {
                "type": "integer",
                "minimum": 0
              },
              "smsSlotsRemapped": {
                "type": "integer",
                "minimum": 0
              },
              "smsSlotsCleared": {
                "type": "integer",
                "minimum": 0
              },
              "emailSlotsRemapped": {
                "type": "integer",
                "minimum": 0
              },
              "emailSlotsCleared": {
                "type": "integer",
                "minimum": 0
              },
              "lostSmsTemplateNames": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "lostEmailTemplateNames": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "isClean": {
                "type": "boolean"
              },
              "eventBreakdown": {
                "type": "array",
                "description": "Present when includeChoices=1",
                "items": {
                  "type": "object"
                }
              },
              "destinationTeamMembers": {
                "type": "array",
                "description": "Active members on destination team for override pickers (includeChoices=1)",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "label": {
                      "type": "string"
                    }
                  }
                }
              },
              "destinationSmsTemplates": {
                "type": "array",
                "description": "SMS templates on destination team (includeChoices=1)",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              },
              "destinationEmailTemplates": {
                "type": "array",
                "description": "Email templates on destination team (includeChoices=1)",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "EmailTemplate": {
        "type": "object",
        "description": "Email template row (list/detail shape when manage permission; list may omit subject/htmlBody/attachments)",
        "required": [
          "id",
          "name",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "subject": {
            "type": "string"
          },
          "htmlBody": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "attachmentsJson": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "description": "Owning team; use with isFromParentTeam in list responses"
          },
          "isFromParentTeam": {
            "type": "boolean",
            "description": "True when template is shared from billing (parent) team"
          }
        }
      },
      "EmailTemplateCreate": {
        "type": "object",
        "required": [
          "name",
          "subject",
          "htmlBody"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "subject": {
            "type": "string",
            "minLength": 1,
            "maxLength": 998
          },
          "htmlBody": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500000
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "attachmentsJson": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
            }
          }
        }
      },
      "EmailTemplateUpdate": {
        "type": "object",
        "description": "At least one property must be provided",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "subject": {
            "type": "string",
            "minLength": 1,
            "maxLength": 998
          },
          "htmlBody": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500000
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "attachmentsJson": {
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
            }
          }
        }
      },
      "EmailTemplateSingleResponse": {
        "type": "object",
        "required": [
          "template"
        ],
        "properties": {
          "template": {
            "$ref": "#/components/schemas/EmailTemplate"
          }
        }
      },
      "EmailTemplatesResponse": {
        "type": "object",
        "required": [
          "templates"
        ],
        "properties": {
          "templates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailTemplate"
            }
          }
        }
      },
      "SmsTemplate": {
        "type": "object",
        "required": [
          "id",
          "name",
          "body",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "maxLength": 1600
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "attachmentsJson": {
            "type": "array",
            "nullable": true,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "isFromParentTeam": {
            "type": "boolean"
          }
        }
      },
      "SmsTemplateCreate": {
        "type": "object",
        "required": [
          "name",
          "body"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1600
          },
          "category": {
            "type": "string",
            "nullable": true,
            "maxLength": 100
          },
          "attachmentsJson": {
            "type": "array",
            "nullable": true,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/MessageTemplateAttachmentItem"
            }
          }
        }
      },
      "SmsTemplateSingleResponse": {
        "type": "object",
        "required": [
          "template"
        ],
        "properties": {
          "template": {
            "$ref": "#/components/schemas/SmsTemplate"
          }
        }
      },
      "SmsTemplatesResponse": {
        "type": "object",
        "required": [
          "templates"
        ],
        "properties": {
          "templates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SmsTemplate"
            }
          }
        }
      },
      "SmsTemplateContextEventOption": {
        "type": "object",
        "description": "A calendar case event option for SMS template merge context (completed events and tasks excluded server-side).",
        "required": [
          "id",
          "title",
          "eventDate",
          "eventType"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "eventDate": {
            "type": "string",
            "format": "date-time"
          },
          "eventType": {
            "type": "string"
          }
        }
      },
      "SmsTemplateContextCaseOption": {
        "type": "object",
        "required": [
          "id",
          "title",
          "caseNumber",
          "events"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "caseNumber": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SmsTemplateContextEventOption"
            }
          }
        }
      },
      "SmsTemplateContextPayload": {
        "type": "object",
        "required": [
          "cases",
          "customCaseFieldNames",
          "customEventFieldNames"
        ],
        "properties": {
          "cases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SmsTemplateContextCaseOption"
            }
          },
          "customCaseFieldNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "customEventFieldNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SmsTemplateContextResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/SmsTemplateContextPayload"
          }
        }
      },
      "SmsTemplateResolveSuccessData": {
        "type": "object",
        "required": [
          "body",
          "mediaUrls",
          "mergeFields",
          "requirements",
          "unresolvedKeys"
        ],
        "properties": {
          "body": {
            "type": "string"
          },
          "mediaUrls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "MMS attachment URLs for Twilio (empty array when template has none)"
          },
          "mergeFields": {
            "type": "object",
            "additionalProperties": true
          },
          "unresolvedKeys": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Placeholder inner keys that resolved to an empty string"
          },
          "requirements": {
            "type": "object",
            "required": [
              "placeholderKeys",
              "requiresCase",
              "requiresEvent"
            ],
            "properties": {
              "placeholderKeys": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "requiresCase": {
                "type": "boolean"
              },
              "requiresEvent": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "EmailTemplateResolveRequest": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "templateId"
            ],
            "description": "eventId (existing CaseEvent) and eventData (draft for new-event preview) are mutually exclusive; send at most one. Omit both when resolving with only client/case context.",
            "properties": {
              "templateId": {
                "type": "string",
                "format": "uuid"
              },
              "clientId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "eventId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Existing non-completed CaseEvent id for merge context; do not combine with eventData. Completed events are not accepted."
              },
              "eventData": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MessageTemplateEventDraft"
                  }
                ],
                "nullable": true,
                "description": "Draft event for preview without a saved CaseEvent; do not combine with eventId"
              },
              "mergeFieldOverrides": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 2000
                },
                "nullable": true,
                "maxProperties": 40
              }
            }
          },
          {
            "not": {
              "type": "object",
              "required": [
                "eventId",
                "eventData"
              ]
            }
          }
        ]
      },
      "SmsTemplateResolveRequest": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "templateId"
            ],
            "description": "Same event context rules as EmailTemplateResolveRequest — at most one of eventId or eventData. Optional teamId scopes merge and template lookup to that team when the caller is a member with clients.view (e.g. messenger team tab).",
            "properties": {
              "teamId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Optional team context; defaults to session team"
              },
              "templateId": {
                "type": "string",
                "format": "uuid"
              },
              "clientId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "caseId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "eventId": {
                "type": "string",
                "format": "uuid",
                "nullable": true,
                "description": "Existing non-completed CaseEvent id for merge context; do not combine with eventData. Completed events are not accepted."
              },
              "eventData": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MessageTemplateEventDraft"
                  }
                ],
                "nullable": true,
                "description": "Draft event for preview without a saved CaseEvent; do not combine with eventId"
              },
              "mergeFieldOverrides": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "maxLength": 2000
                },
                "nullable": true,
                "maxProperties": 40
              }
            }
          },
          {
            "not": {
              "type": "object",
              "required": [
                "eventId",
                "eventData"
              ]
            }
          }
        ]
      },
      "EmailTemplateResolveSuccessData": {
        "type": "object",
        "required": [
          "subject",
          "htmlBody",
          "mergeFields",
          "requirements",
          "unresolvedKeys"
        ],
        "properties": {
          "subject": {
            "type": "string"
          },
          "htmlBody": {
            "type": "string"
          },
          "mergeFields": {
            "type": "object",
            "additionalProperties": true
          },
          "unresolvedKeys": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Placeholder inner keys that resolved to an empty string"
          },
          "requirements": {
            "type": "object",
            "required": [
              "placeholderKeys",
              "requiresCase",
              "requiresEvent"
            ],
            "properties": {
              "placeholderKeys": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "requiresCase": {
                "type": "boolean"
              },
              "requiresEvent": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "EmailTemplateResolveResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/EmailTemplateResolveSuccessData"
          }
        }
      },
      "MessageTemplateAttachmentItem": {
        "type": "object",
        "required": [
          "url",
          "filename",
          "contentType",
          "size"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "pattern": "^https://[^/]+/teams/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/template-attachments/(sms|email)(?:/.*)?$",
            "description": "HTTPS URL on approved blob storage whose path is /teams/{teamId}/template-attachments/sms|email/..."
          },
          "filename": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "contentType": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "size": {
            "type": "integer",
            "minimum": 1,
            "description": "Size in bytes"
          }
        }
      },
      "BlobClientUploadCallbackRequest": {
        "type": "object",
        "required": [
          "type"
        ],
        "description": "Body from @vercel/blob/client handleUpload (token generation or upload completed).",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "blob.generate-client-token",
              "blob.upload-completed"
            ]
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "BlobClientUploadTokenResponse": {
        "type": "object",
        "required": [
          "type",
          "clientToken"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "blob.generate-client-token"
            ]
          },
          "clientToken": {
            "type": "string"
          }
        }
      },
      "BlobClientUploadCompletedAckResponse": {
        "type": "object",
        "required": [
          "type",
          "response"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "blob.upload-completed"
            ]
          },
          "response": {
            "type": "string",
            "enum": [
              "ok"
            ]
          }
        }
      },
      "TemplateAttachmentDeleteRequestSms": {
        "type": "object",
        "required": [
          "kind",
          "urls"
        ],
        "description": "Delete SMS/MMS template-attachment blobs; optional teamId for compose handoff (calls.view on that team).",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "sms"
            ]
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional. When set, URLs must be under this team's SMS template-attachments path and calls.view is required for that team; when omitted, session team and sms_templates.manage apply."
          },
          "urls": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048,
              "pattern": "^https://[^/]+/teams/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/template-attachments/sms(?:/.*)?$"
            }
          }
        }
      },
      "TemplateAttachmentDeleteRequestEmail": {
        "type": "object",
        "required": [
          "kind",
          "urls"
        ],
        "description": "Delete email template-attachment blobs for the session team (email_templates.manage).",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "email"
            ]
          },
          "urls": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048,
              "pattern": "^https://[^/]+/teams/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/template-attachments/email(?:/.*)?$"
            }
          }
        }
      },
      "TemplateAttachmentDeleteRequest": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/TemplateAttachmentDeleteRequestSms"
          },
          {
            "$ref": "#/components/schemas/TemplateAttachmentDeleteRequestEmail"
          }
        ]
      },
      "TemplateAttachmentDeleteResponse": {
        "type": "object",
        "required": [
          "deleted",
          "requested",
          "deletedOk"
        ],
        "properties": {
          "deleted": {
            "type": "integer",
            "description": "URLs validated and sent to blob delete"
          },
          "requested": {
            "type": "integer"
          },
          "deletedOk": {
            "type": "boolean",
            "description": "Whether the blob delete call reported success"
          }
        }
      },
      "KnowledgePageCreateRequest": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "visibility": {
            "type": "string",
            "enum": [
              "TEAM",
              "FIRM"
            ],
            "default": "TEAM"
          },
          "teamId": {
            "type": "string",
            "format": "uuid",
            "description": "Owning team; defaults to current team"
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "description": "Optional parent page (inherits team and default visibility)"
          },
          "tags": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          }
        }
      },
      "KnowledgePagePatchRequest": {
        "type": "object",
        "minProperties": 2,
        "required": [
          "revision"
        ],
        "description": "Must include the expected `revision` for optimistic concurrency, plus at least one of title, contentJson, visibility, parentId, or tags.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "contentJson": {
            "type": "object",
            "description": "TipTap / ProseMirror JSON document"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "TEAM",
              "FIRM"
            ]
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "description": "Expected revision for optimistic concurrency"
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Set to null to make a root page"
          },
          "tags": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          }
        }
      },
      "KnowledgePageResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "page"
            ],
            "properties": {
              "blockedParentIdsForReparent": {
                "type": "array",
                "description": "Present on GET/PATCH single page when the member may reparent; page id plus all descendant ids (invalid parent targets). Omitted on POST create.",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "page": {
                "type": "object",
                "required": [
                  "id",
                  "title",
                  "slug",
                  "teamId",
                  "firmTeamId",
                  "visibility",
                  "revision",
                  "contentJson",
                  "plainText",
                  "locked",
                  "createdAt",
                  "updatedAt",
                  "tags",
                  "parent",
                  "subpages",
                  "canEdit",
                  "canManage",
                  "editable",
                  "lockedByOther",
                  "canUnlock"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "teamId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "firmTeamId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "TEAM",
                      "FIRM"
                    ]
                  },
                  "revision": {
                    "type": "integer"
                  },
                  "contentJson": {
                    "type": "object"
                  },
                  "plainText": {
                    "type": "string"
                  },
                  "locked": {
                    "type": "boolean"
                  },
                  "lockedAt": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  },
                  "lockedByTeamMemberId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "updatedAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "parentId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "parent": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "title": {
                        "type": "string"
                      }
                    }
                  },
                  "subpages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "title"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "title": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "canEdit": {
                    "type": "boolean",
                    "description": "Present on GET/PATCH single page"
                  },
                  "canManage": {
                    "type": "boolean"
                  },
                  "editable": {
                    "type": "boolean",
                    "description": "Whether body can be edited now (lock",
                    "team": null,
                    "etc.)": null
                  },
                  "lockedByOther": {
                    "type": "boolean"
                  },
                  "canUnlock": {
                    "type": "boolean",
                    "description": "Whether this member may release the page lock (owner or knowledge.manage)"
                  }
                }
              }
            }
          }
        }
      },
      "KnowledgePageListResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "pages",
              "pagination"
            ],
            "description": "List and search use limit/offset paging.",
            "properties": {
              "pages": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "id",
                    "title",
                    "slug",
                    "teamId",
                    "visibility",
                    "revision",
                    "updatedAt",
                    "locked",
                    "tags",
                    "canCreateChild"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "title": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "teamId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "visibility": {
                      "type": "string",
                      "enum": [
                        "TEAM",
                        "FIRM"
                      ]
                    },
                    "revision": {
                      "type": "integer"
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "locked": {
                      "type": "boolean"
                    },
                    "lockedByTeamMemberId": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "canCreateChild": {
                      "type": "boolean",
                      "description": "Whether this member may create a subpage under this page (knowledge.edit + lock rules)"
                    },
                    "rank": {
                      "type": "number",
                      "description": "Present when list is search results"
                    },
                    "parentId": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "pagination": {
                "$ref": "#/components/schemas/PaginationMetadata"
              }
            }
          }
        }
      },
      "KnowledgePageVersionCreateRequest": {
        "type": "object",
        "properties": {
          "note": {
            "type": "string",
            "maxLength": 500
          }
        }
      },
      "KnowledgePageVersionListResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "versions"
            ],
            "properties": {
              "versions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "id",
                    "createdAt",
                    "createdByTeamMemberId",
                    "note"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "createdByTeamMemberId": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "note": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      },
      "KnowledgePageVersionResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "version"
            ],
            "properties": {
              "version": {
                "type": "object",
                "required": [
                  "id",
                  "createdAt",
                  "createdByTeamMemberId",
                  "note"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "createdByTeamMemberId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        }
      },
      "KnowledgePageLockRequest": {
        "type": "object",
        "required": [
          "lock"
        ],
        "properties": {
          "lock": {
            "type": "boolean"
          }
        }
      },
      "KnowledgePageImageUploadResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "KnowledgeCommentCreateRequest": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "anchor": {
            "type": "object",
            "nullable": true
          }
        }
      },
      "KnowledgeCommentPatchRequest": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000
          }
        }
      },
      "KnowledgeComment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "pageId": {
            "type": "string",
            "format": "uuid"
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "body": {
            "type": "string"
          },
          "anchor": {
            "type": "object",
            "nullable": true
          },
          "createdByTeamMemberId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "author": {
            "type": "object",
            "properties": {
              "userId": {
                "type": "string",
                "format": "uuid"
              },
              "username": {
                "type": "string"
              }
            }
          }
        }
      },
      "KnowledgeCommentResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "comment"
            ],
            "properties": {
              "comment": {
                "$ref": "#/components/schemas/KnowledgeComment"
              }
            }
          }
        }
      },
      "KnowledgeCommentListResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "comments"
            ],
            "properties": {
              "comments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/KnowledgeComment"
                }
              }
            }
          }
        }
      },
      "DeleteKnowledgeCommentResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "ok"
            ],
            "properties": {
              "ok": {
                "type": "boolean",
                "enum": [
                  true
                ]
              }
            }
          }
        }
      }
    },
    "parameters": {
      "ScopeParam": {
        "in": "query",
        "name": "scope",
        "description": "Notification scope (current team or firm)",
        "schema": {
          "type": "string",
          "enum": [
            "current_team",
            "firm"
          ],
          "default": "current_team"
        }
      },
      "TypeParam": {
        "in": "query",
        "name": "type",
        "description": "Single notification type filter",
        "schema": {
          "type": "string"
        }
      },
      "TypesParam": {
        "in": "query",
        "name": "types",
        "description": "Comma-separated notification type filters",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}