{
  "openapi": "3.1.0",
  "info": {
    "title": "docivault API",
    "version": "2026-11-04",
    "description": "The API behind the docivault web app. Every request acts as the signed-in person and sees only what that person may see in the app."
  },
  "servers": [
    {
      "url": "https://docivault.com"
    }
  ],
  "paths": {
    "/api/activity": {
      "get": {
        "operationId": "getActivity",
        "summary": "Admin-only paged workspace activity feed (audit_logs catalogue) with search, actor, category and date filters.",
        "tags": [
          "Activity"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "sanitizeSearch strips structural chars, min 2 chars, ILIKE over 9 metadata keys + actor name match (<=50 actors)"
            }
          },
          {
            "name": "actor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "all"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date",
              "description": "refine from<=to"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "activity:read"
            ]
          }
        ],
        "x-scope": "activity:read",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{entries:[{id,action,category,tone,actor,verb,subject,subjectState,href,clientName?,createdAt}],pagination:{page,pageSize,total,totalPages}}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/categories": {
      "get": {
        "operationId": "getCategories",
        "summary": "Paged, filterable category list (admin / member / portal contact variants), select-all ids, and upload-picker mode.",
        "tags": [
          "Categories"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 15
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "filterType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "workspace_wide",
                "specific",
                "unassigned"
              ],
              "default": "all"
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name_asc",
                "name_desc",
                "created_asc",
                "created_desc",
                "updated_desc"
              ],
              "default": "name_asc"
            }
          },
          {
            "name": "filterClientIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "NOT uuid-validated, no length cap"
            }
          },
          {
            "name": "createdBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 2000,
              "description": "comma list of 'me' or uuids, refined"
            }
          },
          {
            "name": "isDefault",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "ids_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          },
          {
            "name": "purpose",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "upload"
              ]
            }
          },
          {
            "name": "_t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "cache buster"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "categories:read"
            ]
          }
        ],
        "x-scope": "categories:read",
        "x-roles": [
          "admin",
          "member",
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "{categories:[{id,name,is_default,assign_to_all_clients,created_at,updated_at,created_by,assignments,assignment_summary,created_by_profile,document_count}],availableClients:[{id,name}],pagination:{page,pageSize,total,totalPages},totalSelectable}; ids_only: {ids,totalSelectable}; portal rows lack summary fields. no-store headers"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "post": {
        "operationId": "postCategories",
        "summary": "Create one category (legacy scope body) or one/many categories (flat bulk body, up to 50 names).",
        "tags": [
          "Categories"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "x-roles": [
          "admin",
          "member (own clients only)"
        ],
        "x-rate-limit": "30 per 10 min per user",
        "responses": {
          "201": {
            "description": "legacy: {category:<enrichOne>} ; flat: {created:[{id,name,created_at,updated_at,created_by}],conflicts:[names]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "names": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 50
                  },
                  "assign_to_all_clients": {
                    "type": "boolean",
                    "default": false,
                    "description": "flat body only; admin only"
                  },
                  "client_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  },
                  "scope": {
                    "type": "string",
                    "description": "legacy body; presence of key selects this schema"
                  }
                },
                "required": [
                  "org_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/categories/bulk": {
      "post": {
        "operationId": "postCategoriesBulk",
        "summary": "Bulk soft-delete or bulk reassign (workspace-wide flag / client set) up to 50 categories.",
        "tags": [
          "Categories"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "x-roles": [
          "admin",
          "member (own categories)"
        ],
        "responses": {
          "200": {
            "description": "delete: {deleted:[ids],skipped:[{id,reason}],not_found:[ids]} ; reassign: {updated:[ids],skipped:[{id,reason}]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "delete",
                      "reassign"
                    ]
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50
                  },
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "assign_to_all_clients": {
                    "type": "boolean",
                    "description": "reassign only"
                  },
                  "client_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500,
                    "description": "reassign only; [] = unassign all"
                  }
                },
                "required": [
                  "action",
                  "ids",
                  "org_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/categories/{id}": {
      "delete": {
        "operationId": "deleteCategoriesId",
        "summary": "Soft-delete a category and strip it from every document.",
        "tags": [
          "Categories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "x-roles": [
          "admin",
          "member (own non-workspace-wide categories only)"
        ],
        "responses": {
          "200": {
            "description": "{ok:true}"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "patch": {
        "operationId": "patchCategoriesId",
        "summary": "Rename a category, toggle workspace-wide, replace its client assignments, or transfer ownership (admin).",
        "tags": [
          "Categories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "x-roles": [
          "admin",
          "member (own non-workspace-wide categories only)"
        ],
        "responses": {
          "200": {
            "description": "{ok:true,category:<enrichOne payload>}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "assign_to_all_clients": {
                    "type": "boolean"
                  },
                  "client_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  },
                  "owner_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "admin only; must be active member of org"
                  }
                },
                "required": []
              }
            }
          }
        }
      }
    },
    "/api/categories/{id}/clients": {
      "get": {
        "operationId": "getCategoriesIdClients",
        "summary": "Preview of the client names behind a category count (up to limit, plus true total).",
        "tags": [
          "Categories"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 8
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "categories:read"
            ]
          }
        ],
        "x-scope": "categories:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "{workspace_wide:boolean,clients:[{id,name}],total:number}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/clients": {
      "get": {
        "operationId": "getClients",
        "summary": "Unpaged list of the workspace's non-archived clients (id, name, email, company, status, assigned team) for the assign-clients and invite-team pickers.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "{clients:[{id,name,email,company,status,created_at,client_assignments:[{user_id,profiles:{full_name}}]}]} ordered created_at desc; archived excluded"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "post": {
        "operationId": "postClients",
        "summary": "Admin creates a client with 1-100 contacts (one or more primary), optional photo and team assignees; every contact is invited (inline, or deferred to the per-contact invite route). Invitations are sent afterwards, one request per contact (never inside this request).",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "30/min",
        "responses": {
          "201": {
            "description": "{client:{...clients row}, inviteFailures:string[] emails, invitesSkipped:number}; with deferInvites: {client, contacts:[{id,email}], inviteFailures:[], invitesSkipped:0, invitesDeferred:true}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "company": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "contacts": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "primaryIndex": {
                    "type": "integer",
                    "minimum": 0,
                    "default": 0
                  },
                  "primaryIndexes": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "maxItems": 100,
                    "description": "wins over primaryIndex; each must be < contacts.length"
                  },
                  "assignedTeamUserIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "NO max length"
                  },
                  "deferInvites": {
                    "type": "boolean"
                  },
                  "avatarKey": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "must equal pending-avatars/<orgId>/<digits>-<12hex>.(png|jpg|webp)"
                  }
                },
                "required": [
                  "org_id",
                  "name",
                  "contacts"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/bulk/archive": {
      "post": {
        "operationId": "postClientsBulkArchive",
        "summary": "Admin archives up to 25 clients at once and signs every linked portal contact out globally.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "10/min",
        "responses": {
          "200": {
            "description": "{succeeded:string[], failed:[{id, reason:'not_found'|a short reason}]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 25
                  },
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "clientIds",
                  "org_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/bulk/delete": {
      "post": {
        "operationId": "postClientsBulkDelete",
        "summary": "Admin hard-deletes up to 25 clients (cascade to contacts, documents rows) after signing their linked portal users out globally.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "10/min",
        "responses": {
          "200": {
            "description": "{succeeded:string[], failed:[{id, reason:'not_found'|a short reason}]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 25
                  },
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "clientIds",
                  "org_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/bulk/unarchive": {
      "post": {
        "operationId": "postClientsBulkUnarchive",
        "summary": "Admin restores up to 25 archived clients at once.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "10/min",
        "responses": {
          "200": {
            "description": "{succeeded:string[], failed:[{id, reason:'not_found'|a short reason}]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 25
                  },
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "clientIds",
                  "org_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/contacts/check": {
      "post": {
        "operationId": "postClientsContactsCheck",
        "summary": "Admin pre-check of up to 100 candidate contact emails: ok | invalid | team | pending_team | existing contact (with its client name).",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "60/min",
        "responses": {
          "200": {
            "description": "{results:[{email,status:'ok'|'invalid'|'team'|'pending_team'|'contact', clientId?, clientName?}]} same order as input"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "emails": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  }
                },
                "required": [
                  "org_id",
                  "emails"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/list": {
      "get": {
        "operationId": "getClientsList",
        "summary": "Paginated, searchable, sortable, filterable enriched Clients table (contacts, team, doc/category counts, derived status, signed avatars); ids_only=true returns the Select-all id set (admin only, max 100).",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "no max length; escapeIlike then or() with ,()\" stripped; matches client name/company and contact name/email"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "archived",
                "active",
                "invited",
                "inactive"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "recently_added",
                "oldest_added",
                "name_asc",
                "name_desc",
                "company_asc",
                "company_desc",
                "last_active",
                "last_active_desc",
                "last_active_asc"
              ],
              "default": "recently_added"
            }
          },
          {
            "name": "assignedUserIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "no max count"
            }
          },
          {
            "name": "clientIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "categoryIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "ids_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "{rows:[EnrichedClient{id,name,email,company,status,rawStatus,created_at,last_active,avatarUrl(signed),primaryContactUserId,contacts[top4],totalContacts,assignedTeam[],pendingTeamCount,orgAdminCount,teamCount,docCount,categoryCount}], total, page, pageSize}; ids_only: {ids:[{id,name,status}], total, capped}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/clients/picker": {
      "get": {
        "operationId": "getClientsPicker",
        "summary": "Lightweight name-ordered, offset-paged {id,name} client list for pickers and filter drawers; scope=mine limits a member to assigned clients.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "mine"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "ilike name with escapeLike"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "{clients:[{id,name}], hasMore:boolean}, ordered by name"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/clients/{clientId}": {
      "delete": {
        "operationId": "deleteClientsClientId",
        "summary": "Hard-delete a client (typed name confirmation), signing out its linked portal users",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{ok:true}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "confirmName": {
                    "type": "string",
                    "description": "trimmed, exact match; empty/invalid JSON body tolerated -> 400 NAME_MISMATCH; no Zod"
                  }
                },
                "required": [
                  "confirmName"
                ]
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchClientsClientId",
        "summary": "Edit client name/company/photo and replace its assigned team (admin only)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{client:<clients row>}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "trimmed"
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "'' clears"
                  },
                  "assignedTeamUserIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "NO max length"
                  },
                  "avatarR2Key": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "null/'' clears; else must be pending-avatars/<orgId>/<ms>-<12hex>.(png|jpg|webp)"
                  }
                },
                "required": []
              }
            }
          }
        }
      }
    },
    "/api/clients/{clientId}/archive": {
      "post": {
        "operationId": "postClientsClientIdArchive",
        "summary": "Archive a client (status archived) and sign out all linked portal users",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{client:<clients.* + client_contacts[{user_id}]>}"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/clients/{clientId}/contacts": {
      "get": {
        "operationId": "getClientsClientIdContacts",
        "summary": "List non-archived contacts of a client with derived status and primary flag",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "admin",
          "member (assigned only)"
        ],
        "responses": {
          "200": {
            "description": "{contacts:[{id,email,name,userId,avatarUrl,lastActiveAt,invited_at,portal_enabled,createdAt,isPrimary,status}]}"
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "post": {
        "operationId": "postClientsClientIdContacts",
        "summary": "Add a contact to a client and send the portal invite (admin, or a PRIMARY portal contact for their own client)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin",
          "portal contact (primary only)"
        ],
        "x-rate-limit": "30/min",
        "responses": {
          "201": {
            "description": "{contact:{id,email,name,user_id,invited_at,portal_enabled}} plus inviteFailed:true when no mail went out (no reason given)"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "trim+lowercase+email"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "isPrimary": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        }
      }
    },
    "/api/clients/{clientId}/contacts/{contactId}": {
      "delete": {
        "operationId": "deleteClientsClientIdContactsContactId",
        "summary": "Remove a contact (admin), remove a colleague (primary portal contact) or leave (any portal contact)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin",
          "portal contact (primary for colleagues; any for self/leave)"
        ],
        "responses": {
          "200": {
            "description": "{ok:true} (admin) | {ok:true,selfLeft:true} (self leave)"
          },
          "204": {
            "description": "empty (primary removed a colleague)"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        }
      },
      "patch": {
        "operationId": "patchClientsClientIdContactsContactId",
        "summary": "Change a contact's email, make/unmake primary, or archive/restore it (name is never editable)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin",
          "portal contact (own row; primary for colleagues/primary flag)"
        ],
        "x-rate-limit": "10/h",
        "responses": {
          "200": {
            "description": "{ok:true,contact:{id,name,email[,isPrimary|archived]}}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "trim+lowercase+email"
                  },
                  "isPrimary": {
                    "type": "boolean",
                    "description": "alone"
                  },
                  "archived": {
                    "type": "boolean",
                    "description": "alone; admin only"
                  },
                  "name": {
                    "type": "string",
                    "description": "refine: any non-undefined name (incl null) => 400"
                  }
                },
                "required": []
              }
            }
          }
        }
      }
    },
    "/api/clients/{clientId}/contacts/{contactId}/invite": {
      "post": {
        "operationId": "postClientsClientIdContactsContactIdInvite",
        "summary": "Send the FIRST invite to a just-added contact (deferInvites flow)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "x-rate-limit": "1200/h",
        "responses": {
          "200": {
            "description": "{ok:true} | {ok:true,already:true}"
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "502": {
            "description": "Temporarily unavailable."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        }
      }
    },
    "/api/clients/{clientId}/contacts/{contactId}/resend-invite": {
      "post": {
        "operationId": "postClientsClientIdContactsContactIdResendInvite",
        "summary": "Resend a pending portal invite (admin, or a primary portal contact for a colleague)",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin",
          "portal contact (primary for colleagues)"
        ],
        "x-rate-limit": "1/min and 5/24h",
        "responses": {
          "200": {
            "description": "{ok:true,email}"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "502": {
            "description": "Temporarily unavailable."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        }
      }
    },
    "/api/clients/{clientId}/unarchive": {
      "post": {
        "operationId": "postClientsClientIdUnarchive",
        "summary": "Restore an archived client to active",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:write"
            ]
          }
        ],
        "x-scope": "clients:write",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{client:<clients.* + client_contacts[{user_id}]>}"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/dashboard": {
      "get": {
        "operationId": "getDashboard",
        "summary": "Firm-side dashboard payload (admin or member summary) for a workspace.",
        "tags": [
          "Dashboard"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "today",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "AdminDashboard {role:'admin',kpis,attention:{overdue,failed,stale},recentDocuments,activity} | MemberDashboard {role:'member',kpis,requests,myClients,recentDocuments,myUploads,feed}; no-store"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/document-requests": {
      "get": {
        "operationId": "getDocumentRequests",
        "summary": "List all requests of ONE client (portal list + per-client firm view)",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "requests:read"
            ]
          }
        ],
        "x-scope": "requests:read",
        "x-roles": [
          "admin",
          "member (assigned)",
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "{requests:RequestRow[], truncated?:true}  newest first, cap 5000 paged by 1000"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "post": {
        "operationId": "postDocumentRequests",
        "summary": "Create a document request (firm->client or client->firm) with optional opening attachments",
        "tags": [
          "Requests"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "requests:write"
            ]
          }
        ],
        "x-scope": "requests:write",
        "x-roles": [
          "admin",
          "member (assigned)",
          "portal contact"
        ],
        "x-rate-limit": "30/h",
        "responses": {
          "201": {
            "description": "{request:RequestRow, attachments_failed?:true}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "org_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 50,
                    "description": "name trim 1..200, strict"
                  },
                  "due_date": {
                    "type": "string",
                    "format": "date",
                    "description": "not past (isPastDueDate), not too far (isTooFarDueDate)"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1000
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "firm_to_client",
                      "client_to_firm"
                    ],
                    "default": "firm_to_client"
                  },
                  "document_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  }
                },
                "required": [
                  "org_id",
                  "client_id",
                  "items"
                ]
              }
            }
          }
        }
      }
    },
    "/api/document-requests/org": {
      "get": {
        "operationId": "getDocumentRequestsOrg",
        "summary": "Org-wide request list for the Requests tab (admin all, member assigned clients)",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "requests:read"
            ]
          }
        ],
        "x-scope": "requests:read",
        "x-roles": [
          "admin",
          "member (assigned clients)"
        ],
        "responses": {
          "200": {
            "description": "{requests:RequestRow[], truncated?:true, closed_truncated?:true}; open rows to 5000, closed/cancelled newest 500"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/document-requests/{requestId}": {
      "get": {
        "operationId": "getDocumentRequestsRequestId",
        "summary": "Request thread: request, opening attachments, messages, allowed actions for the viewer",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "requests:read"
            ]
          }
        ],
        "x-scope": "requests:read",
        "x-roles": [
          "admin",
          "member (assigned)",
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "{request:RequestRow, attachments, messages, allowed_actions:string[]}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/document-requests/{requestId}/messages": {
      "post": {
        "operationId": "postDocumentRequestsRequestIdMessages",
        "summary": "Post a reply/comment/approve/reopen/cancel on a request thread (state machine in RPC)",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "requests:write"
            ]
          }
        ],
        "x-scope": "requests:write",
        "x-roles": [
          "admin",
          "member (assigned)",
          "portal contact"
        ],
        "x-rate-limit": "120/h",
        "responses": {
          "201": {
            "description": "{ok:true, message?, request?}  (always 201 after the RPC commits; thin {ok:true} if re-read fails)"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "reply",
                      "comment",
                      "reopen",
                      "approve",
                      "cancel"
                    ]
                  },
                  "body": {
                    "type": "string",
                    "maxLength": 2000,
                    "default": "",
                    "description": "trim; reopen requires text; reply/comment require text or a file"
                  },
                  "document_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10,
                    "description": "not allowed with approve/cancel"
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  }
                },
                "required": [
                  "kind"
                ]
              }
            }
          }
        }
      }
    },
    "/api/files": {
      "get": {
        "operationId": "getFiles",
        "summary": "List one client's documents in one space (keyset paged); used by the portal and by pickers. Org Documents tab uses /api/files/workspace instead.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "space",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "private",
                "shared",
                "client"
              ]
            }
          },
          {
            "name": "archived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "false"
            }
          },
          {
            "name": "category_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "oldest",
                "az",
                "za"
              ],
              "default": "newest"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member",
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "{documents:[{id,name,display_name,sort_name,file_size,mime_type,uploaded_by,created_at,archived_at,category_ids,space,client_id,processing_status,processing_retry_after,rendition_mime,description,profiles:{full_name}|null,failure_reason (team only),request_count}],has_more,next_cursor}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/files/bulk": {
      "post": {
        "operationId": "postFilesBulk",
        "summary": "Archive / restore / delete / move space (optionally attach a client) / add or remove categories for up to 100 documents in one request, with per-id verdicts and a dry_run mode.",
        "tags": [
          "Documents"
        ],
        "parameters": [],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:write"
            ]
          }
        ],
        "x-scope": "documents:write",
        "x-roles": [
          "admin",
          "member"
        ],
        "x-rate-limit": "300 per 5 min per user (shared with edit)",
        "responses": {
          "200": {
            "description": "{results:[{id,ok,reason?}],counts:{ok,skipped,failed},contacts?,comments?(dry_run move to Shared)}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "413": {
            "description": "Too large."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "archive",
                      "restore",
                      "delete",
                      "move",
                      "add_categories",
                      "remove_categories"
                    ]
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  },
                  "dry_run": {
                    "type": "boolean"
                  },
                  "space": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "only with space=client"
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "action",
                  "ids",
                  "space",
                  "category_ids"
                ]
              }
            }
          }
        }
      }
    },
    "/api/files/workspace": {
      "get": {
        "operationId": "getFilesWorkspace",
        "summary": "Org Documents tab: one flat, role-scoped, keyset-paged list across spaces/contacts/categories/uploaders with search, doc_id deep-link lookup and exact_name upload-conflict lookup.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "space",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "private",
                "shared",
                "client"
              ]
            }
          },
          {
            "name": "spaces[]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "maxItems": 3
            }
          },
          {
            "name": "client_ids[]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 200
            }
          },
          {
            "name": "no_contact",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "false"
            }
          },
          {
            "name": "category_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "legacy"
            }
          },
          {
            "name": "category_ids[]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50
            }
          },
          {
            "name": "uploaded_by[]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 100
            }
          },
          {
            "name": "uncategorized",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "archived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "hide archived"
            }
          },
          {
            "name": "processing",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "failed"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "oldest",
                "az",
                "za"
              ],
              "default": "newest"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 25
            }
          },
          {
            "name": "doc_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "lookup mode"
            }
          },
          {
            "name": "exact_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500,
              "description": "lookup mode, requires exactly one space"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member",
          "staff_admin(fallback)"
        ],
        "responses": {
          "200": {
            "description": "{documents:[{id,name,display_name,description,file_size,mime_type,space,created_at,archived_at,category_ids,client_id,client_name,client_archived,client_avatar_url,uploaded_by,uploader_name,uploader_avatar_url,uploader_is_contact,processing_status,rendition_mime,rendition_size,processing_retry_after,request_count,failure_reason}],has_more,next_cursor}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/files/{fileId}": {
      "delete": {
        "operationId": "deleteFilesFileId",
        "summary": "Hard-delete a document: row first, then every stored byte; admin or uploader only.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:write"
            ]
          }
        ],
        "x-scope": "documents:write",
        "x-roles": [
          "admin",
          "member (uploader)"
        ],
        "responses": {
          "200": {
            "description": "{success:true}"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      },
      "patch": {
        "operationId": "patchFilesFileId",
        "summary": "Edit a document: rename, description, notes, move space, archive/restore, set categories, or change its client (with an optional space move).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:write"
            ]
          }
        ],
        "x-scope": "documents:write",
        "x-roles": [
          "admin",
          "member"
        ],
        "x-rate-limit": "300 per 5 min per user (shared with bulk)",
        "responses": {
          "200": {
            "description": "{document:{id,name,display_name,description,space,notes,archived_at,category_ids,file_size,mime_type,uploaded_by,created_at,client_id}}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "not trimmed, no hidden-char check"
                  },
                  "space": {
                    "type": "string",
                    "enum": [
                      "private",
                      "shared",
                      "client"
                    ]
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "description": {
                    "type": "string",
                    "description": "descriptionField, max 500, blank clears"
                  },
                  "archived": {
                    "type": "boolean"
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "only combined with space"
                  }
                },
                "required": []
              }
            }
          }
        }
      }
    },
    "/api/files/{fileId}/download": {
      "get": {
        "operationId": "getFilesFileIdDownload",
        "summary": "The one audited way to get a document as a file: decrypts server-side and streams the original, an Office PDF rendition, or a PDF built from image pages.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "original",
                "pdf"
              ],
              "maxLength": 16,
              "default": "first offered format"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member (granted)",
          "portal contact (granted)"
        ],
        "x-rate-limit": "60 / 1h per user_id",
        "responses": {
          "200": {
            "description": "binary stream; Content-Disposition attachment, Content-Length, Cache-Control private,no-store, nosniff"
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "409": {
            "description": "Conflict with the current state of the resource."
          },
          "422": {
            "description": "Invalid input."
          },
          "429": {
            "description": "Too many requests; retry after the delay in the response."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          },
          "503": {
            "description": "Temporarily unavailable."
          }
        }
      }
    },
    "/api/files/{fileId}/download-access": {
      "put": {
        "operationId": "putFilesFileIdDownloadAccess",
        "summary": "Set who may download a document (team flag, contacts flag, named people). Admin only; replaces grants atomically.",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:write"
            ]
          }
        ],
        "x-scope": "documents:write",
        "x-roles": [
          "admin"
        ],
        "responses": {
          "200": {
            "description": "{team,contacts,user_ids}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "404": {
            "description": "Not found."
          },
          "422": {
            "description": "Invalid input."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "team": {
                    "type": "boolean"
                  },
                  "contacts": {
                    "type": "boolean"
                  },
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50
                  }
                },
                "required": [
                  "team",
                  "contacts",
                  "user_ids"
                ]
              }
            }
          }
        }
      }
    },
    "/api/files/{fileId}/download-log": {
      "get": {
        "operationId": "getFilesFileIdDownloadLog",
        "summary": "Keyset-paged log of who downloaded a document (admin: all; member: own; portal: 404).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member (own rows)"
        ],
        "responses": {
          "200": {
            "description": "{items:[{id,at,event:'download',who:{kind:'member'|'contact',name,avatar?}}],nextCursor,total(first page only),scope:'own'|'all'}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/files/{fileId}/requests": {
      "get": {
        "operationId": "getFilesFileIdRequests",
        "summary": "Document requests this document is attached to (for the details panel and archive/move confirms).",
        "tags": [
          "Documents"
        ],
        "parameters": [
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "admin",
          "member",
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "{requests:[...],total}"
          },
          "401": {
            "description": "Not signed in."
          },
          "404": {
            "description": "Not found."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/members/contacts": {
      "get": {
        "operationId": "getMembersContacts",
        "summary": "Org-wide client-contacts directory (one row per contact with client name/status, derived portal status, last active, primary flags), paged/sorted/filtered; ids_only returns the select-all set.",
        "tags": [
          "Clients and contacts"
        ],
        "parameters": [
          {
            "name": "org_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "ids_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive",
                "archived",
                "invited"
              ]
            }
          },
          {
            "name": "clientIds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name_asc",
                "name_desc",
                "status_asc",
                "status_desc",
                "invited_desc",
                "invited_asc",
                "added_desc",
                "added_asc"
              ],
              "default": "name_asc"
            }
          },
          {
            "name": "contactType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "primary",
                "regular"
              ],
              "default": "all"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "admin",
          "member"
        ],
        "responses": {
          "200": {
            "description": "{rows:[ContactRow{id,client_id,client_name,client_status,name,email,user_id,invited_at,portal_enabled,created_at,avatar_url,pending_avatar_url,avatar_updated_at,last_active_at,status,is_self,is_primary,client_primary_count,contact_archived}], total, totalPages}; ids_only: {ids:[{id,client_id}], totalSelectable}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/portal/dashboard": {
      "get": {
        "operationId": "getPortalDashboard",
        "summary": "Client-portal dashboard (documents, needs-action requests, shared docs, firm team, feed) for one client.",
        "tags": [
          "Client portal"
        ],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "today",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "documents:read"
            ]
          }
        ],
        "x-scope": "documents:read",
        "x-roles": [
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "PortalDashboard {role:'client',kpis:{documents,needsAction,overdue},needsAction,sharedWithYou,firmTeam,feed}; no-store"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    },
    "/api/portal/people": {
      "get": {
        "operationId": "getPortalPeople",
        "summary": "Portal Members page data: 'firm' tab (admins + assigned team incl. pending) or 'colleagues' tab (contacts of the caller's client), filtered/sorted/paged in memory.",
        "tags": [
          "Client portal"
        ],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tab",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "firm",
                "colleagues"
              ]
            }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name_asc",
                "name_desc",
                "activity_desc",
                "activity_asc",
                "status_asc",
                "status_desc"
              ],
              "default": "name_asc"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "active",
                "inactive",
                "invited",
                "archived"
              ],
              "default": "all"
            }
          },
          {
            "name": "contactType",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "primary",
                "regular"
              ],
              "default": "all"
            }
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "admin",
                "member"
              ],
              "default": "all"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "security": [
          {
            "sessionAuth": []
          },
          {
            "bearerAuth": [
              "clients:read"
            ]
          }
        ],
        "x-scope": "clients:read",
        "x-roles": [
          "portal contact"
        ],
        "responses": {
          "200": {
            "description": "firm: {rows:[FirmRow],total,totalPages} ; colleagues: {rows:[PortalColleagueRow],total,totalPages,viewerIsPrimary,primaryCount}"
          },
          "400": {
            "description": "Invalid input."
          },
          "401": {
            "description": "Not signed in."
          },
          "403": {
            "description": "Not allowed."
          },
          "500": {
            "description": "Something went wrong; safe to retry."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "The signed-in browser session."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dvt_...",
        "description": "A personal API token (Authorization: Bearer dvt_...). Acts as its owner, never with more access than the owner has in the app, and only on endpoints that list a scope the token holds."
      }
    }
  }
}