{
  "openapi": "3.1.0",
  "info": {
    "title": "Thicket",
    "version": "2026-10-06",
    "description": "Thicket's versioned REST API. The web app runs on the same routes, so everything the product does is here. Human-readable reference: https://www.thickethq.com/developers/api",
    "contact": {
      "name": "Thicket support",
      "email": "support@thickethq.com"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://www.thickethq.com"
    }
  ],
  "security": [
    {
      "personalAccessToken": []
    },
    {
      "sessionToken": []
    },
    {
      "oauthBearer": []
    }
  ],
  "tags": [
    {
      "name": "Authorization"
    },
    {
      "name": "Organizations"
    },
    {
      "name": "Projects"
    },
    {
      "name": "Recordings"
    },
    {
      "name": "Check-ins"
    },
    {
      "name": "Clients"
    },
    {
      "name": "Progress & health"
    },
    {
      "name": "Chat"
    },
    {
      "name": "Calendar"
    },
    {
      "name": "Files"
    },
    {
      "name": "People"
    },
    {
      "name": "Templates"
    },
    {
      "name": "Notifications"
    },
    {
      "name": "Search & reports"
    },
    {
      "name": "Timesheets"
    },
    {
      "name": "Trash & admin"
    },
    {
      "name": "Billing"
    },
    {
      "name": "Me"
    }
  ],
  "paths": {
    "/api/v1/{orgSlug}/account": {
      "get": {
        "operationId": "getAccount",
        "tags": [
          "Organizations"
        ],
        "summary": "The account document",
        "description": "Identity, owners, plan limits, can-do flags, and settings for the caller's organization (any member). `logo` is omitted when unset.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgAccount"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "renameAccount",
        "tags": [
          "Organizations"
        ],
        "summary": "Rename the account",
        "description": "Owner only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted. Resending the current name is always accepted, even one set before this rule."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renamed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "cancelAccount",
        "tags": [
          "Organizations"
        ],
        "summary": "Cancel the account",
        "description": "Owner only, effective immediately: the org turns invisible everywhere, any Stripe subscription is cancelled, and every owner gets a confirmation email. Data is retained for a 30-day restore window (POST /api/v1/orgs/{orgId}/restore); after that the purge task hard-deletes rows and storage.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/account/export": {
      "get": {
        "operationId": "exportAccount",
        "tags": [
          "Organizations"
        ],
        "summary": "Export the account as JSON",
        "description": "Owner only; the response is a JSON attachment. `?projects=id,id` limits content to those projects (people, companies, and groups always ride along). Private chats and personal notes are never included; use /account/export/chats for transcripts.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projects",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated project ids to limit the content"
          }
        ],
        "responses": {
          "200": {
            "description": "The export document (Content-Disposition: attachment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/account/export/chats": {
      "get": {
        "operationId": "exportPrivateChats",
        "tags": [
          "Organizations"
        ],
        "summary": "Export private-chat transcripts",
        "description": "Owner only; deliberately split from the account export because of the privacy weight it carries. Transcripts (participants plus author, timestamp, and content lines) for the chosen conversations, as a JSON attachment.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated DM recording ids, at least one"
          }
        ],
        "responses": {
          "200": {
            "description": "The transcripts document (Content-Disposition: attachment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/account/logo": {
      "put": {
        "operationId": "uploadAccountLogo",
        "tags": [
          "Organizations"
        ],
        "summary": "Upload or replace the account logo",
        "description": "Admin and up. Multipart upload, `logo` field, any decodable image up to 5 MB; the server re-encodes it to a square webp.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "logo"
                ],
                "properties": {
                  "logo": {
                    "type": "string",
                    "format": "binary",
                    "description": "Any decodable image, 5 MB max"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Stored"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteAccountLogo",
        "tags": [
          "Organizations"
        ],
        "summary": "Remove the account logo",
        "description": "Admin and up.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/account/storage": {
      "get": {
        "operationId": "getStorageUsage",
        "tags": [
          "Organizations"
        ],
        "summary": "Storage usage vs the plan limit",
        "description": "Total usage, the plan's limit, and the largest files. Private-chat and unposted-draft uploads count toward usage but are never listed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage and the largest files",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StorageUsage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/activity": {
      "get": {
        "operationId": "listActivity",
        "tags": [
          "Search & reports"
        ],
        "summary": "The account-wide activity timeline",
        "description": "Everything that happened across your accessible projects, newest first, ready to group by day client-side. Page with `per_page` and follow the `Link: rel=\"next\"` header, whose URL carries the next page's opaque `cursor` (rows written between requests neither repeat nor shift pages); `page` still answers. Same visibility rules as the Latest-activity page: trashed content and non-client-visible recordings filtered, chat and DM lines never logged. Filters: `project_id`, `person_id` (the actor), `starred=1` (your starred projects), `since` (ISO datetime), `q` (matches title, actor name, or content text).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "name": "project_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "person_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only this actor's events"
          },
          {
            "name": "starred",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Pass 1 to limit to your starred projects"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only events at or after this instant"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list of event actions, e.g. card_added,move (a container-watch poll)"
          },
          {
            "name": "recording_type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only events about recordings of this type"
          },
          {
            "name": "parent_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only events about recordings inside this container (a column, a list): the container-watch poll"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Keyword filter: title, actor name, or content text"
          }
        ],
        "responses": {
          "200": {
            "description": "Activity events, newest first (default page size 50)",
            "headers": {
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ActivityEvent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/agents": {
      "get": {
        "operationId": "listAgents",
        "tags": [
          "Agents"
        ],
        "summary": "List named agents",
        "description": "People only. Owners/admins manage all agents; operators can see their agents and connections. Project names and request threads are filtered to caller access.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/NamedAgent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Create a named agent",
        "description": "Owners/admins only. Creates an agent member with no password or personal tokens. Its operators must be active human teammates. Only explicitly selected projects are accessible.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "membership_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "required": [
                    "membership_id"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentInput"
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/agents/{membershipId}": {
      "patch": {
        "operationId": "updateAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Update or deactivate an agent",
        "description": "Owners/admins only. Deactivation revokes connections and interrupts running requests. Reactivation requires a new connection. Name/operators/project updates replace the supplied lists; existing projects hidden from the caller are preserved.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "membership_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "required": [
                    "membership_id"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/AgentInput"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "enabled"
                    ]
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/agents/{membershipId}/connections/{connectionId}": {
      "delete": {
        "operationId": "disconnectAgent",
        "tags": [
          "Agents"
        ],
        "summary": "Disconnect an agent installation",
        "description": "Owners/admins or an agent operator. Issued tokens stop working on the next request. A request already in progress may finish.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "connectionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Success"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/agents/{membershipId}/requests/{requestId}": {
      "post": {
        "operationId": "retryAgentRequest",
        "tags": [
          "Agents"
        ],
        "summary": "Retry interrupted or failed work",
        "description": "Owners/admins only. Review the original thread first because earlier work may already have succeeded. Authorization and source freshness are rechecked before dispatch.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "retried": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "retried"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/billing": {
      "get": {
        "operationId": "getBilling",
        "tags": [
          "Billing"
        ],
        "summary": "Current plan, status, and usage",
        "description": "Tier, subscription status, trial and period dates, plus usage against the plan's limits. In `limits`, -1 means unlimited; one `people` pool covers members and guests alike, clients included.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The billing document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/billing/change-plan": {
      "post": {
        "operationId": "changePlan",
        "tags": [
          "Billing"
        ],
        "summary": "Switch plans on the live subscription",
        "description": "Owner only. Swaps the price on the existing Stripe subscription with prorations, never a second subscription, and syncs the local row in-request so the change reads back immediately. 404 when there is no live subscription; 422 when the account is already on that plan. A downgrade below the org's active-project count is allowed: the org lands over-limit and the home chooser archives projects down, restorably.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "pro"
                    ]
                  },
                  "billing_interval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "yearly"
                    ],
                    "default": "monthly"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The synced plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "billing_interval",
                    "status"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "enum": [
                        "free",
                        "starter",
                        "pro",
                        "comped"
                      ]
                    },
                    "billing_interval": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "monthly",
                        "yearly",
                        null
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "trialing",
                        "past_due",
                        "cancelled",
                        "pending",
                        "comped",
                        "lapsed"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/billing/create-checkout": {
      "post": {
        "operationId": "createCheckout",
        "tags": [
          "Billing"
        ],
        "summary": "Create a Stripe Checkout session",
        "description": "Owner only. Verifies the price (from GET /api/billing/prices) and returns a Checkout URL to redirect the buyer to. The signup trial is the only trial an org ever gets: a session created mid-trial carries a trial_end aligned to the existing end date, never restarted, and every other org is charged at checkout. Under 48 hours of trial left checks out as an immediate charge.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "price_id"
                ],
                "properties": {
                  "price_id": {
                    "type": "string",
                    "minLength": 1
                  },
                  "billing_interval": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "yearly"
                    ],
                    "default": "monthly"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The Checkout session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uri",
                      "description": "Redirect the buyer here"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown, inactive, or non-Thicket price (code bad_request)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/billing/create-portal": {
      "post": {
        "operationId": "createBillingPortal",
        "tags": [
          "Billing"
        ],
        "summary": "Create a Stripe customer portal session",
        "description": "Owner only: payment method, invoices, and cancellation. The optional `{\"flow\": \"subscription_cancel\"}` body deep-links the portal's cancel-subscription confirmation (404 without a subscription). Plan switching is not portal work; it rides /billing/change-plan.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "flow": {
                    "type": "string",
                    "enum": [
                      "subscription_cancel"
                    ],
                    "description": "Deep-link the portal's cancel-subscription confirmation"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The portal session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Redirect the owner here"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/bulletins": {
      "get": {
        "operationId": "listBulletins",
        "tags": [
          "Notifications"
        ],
        "summary": "The announcements archive",
        "description": "Published platform announcements, newest first: global content read through an org-scoped URL, personalized per viewer ({{first_name}} interpolated, excerpts capped at 200 chars).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Published announcements, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "bulletins"
                  ],
                  "properties": {
                    "bulletins": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BulletinSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/bulletins/{bulletinId}": {
      "get": {
        "operationId": "getBulletin",
        "tags": [
          "Notifications"
        ],
        "summary": "One announcement",
        "description": "The way to open an `action: \"announced\"` notification row (those carry a bulletin_id instead of a recording_id). Published bulletins read for anyone in the org; a draft reads only for someone holding their own tray row for it (a test send), everyone else gets 404. Reading marks the caller's notification row read, same as opening the page.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "bulletinId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The announcement, personalized for the caller",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bulletin"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar": {
      "get": {
        "operationId": "listCalendarItems",
        "tags": [
          "Calendar"
        ],
        "summary": "The global calendar window",
        "description": "Events (and optionally due tasks) across every accessible project's calendar plus the account calendar. Recurring events expand at read time: each occurrence arrives as its own row carrying `occurrence_key`, the anchor-zone day key the occurrences DELETE endpoint speaks. `from`/`to` accept ISO 8601 with an explicit offset, or a zoneless wall clock interpreted in the caller's resolved zone (their /me timezone preference, then the tz cookie, then UTC).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window start; offset or zoneless wall clock"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Window end"
          },
          {
            "name": "just",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "me",
                "everyone"
              ],
              "default": "everyone"
            },
            "description": "`me`: only items you created or are invited to"
          },
          {
            "name": "tasks",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Include due to-dos and cards (per-request view state, not persisted)"
          },
          {
            "name": "projects",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated project ids to limit to"
          },
          {
            "name": "starred",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Limit to your starred projects"
          }
        ],
        "responses": {
          "200": {
            "description": "Items in the window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CalendarOccurrence"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "post": {
        "operationId": "createCalendarEvent",
        "tags": [
          "Calendar"
        ],
        "summary": "Create an event",
        "description": "Invitees (`participant_ids`) are notified. `project_id` null or omitted targets the account calendar: org-level events with no project, non-clients only. Datetimes accept an explicit offset or a zoneless wall clock resolved in the caller's zone. All-day events are floating dates, encoded T00:00:00Z to T23:59:59Z by convention.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "starts_at"
                ],
                "properties": {
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "null or omitted: the account calendar"
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 10000,
                    "description": "Plain-text notes, converted to HTML"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich notes (sanitized); wins over `content` when both are sent"
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "all_day": {
                    "type": "boolean",
                    "default": false
                  },
                  "link": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri",
                    "description": "A join link (video call etc.); reminders carry it"
                  },
                  "circled": {
                    "type": "boolean",
                    "default": false
                  },
                  "recurrence": {
                    "$ref": "#/components/schemas/CalendarRecurrence",
                    "description": "The repeat schedule; null means the event does not recur (clears it on update)"
                  },
                  "participant_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100,
                    "description": "Membership ids to invite; they are notified"
                  },
                  "visible_to_clients": {
                    "type": "boolean",
                    "description": "A project event's \"Who should see this?\"; omitted means the project's `share_new_items_with_clients` default. Ignored on the account calendar"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created event",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/{eventId}": {
      "patch": {
        "operationId": "updateCalendarEvent",
        "tags": [
          "Calendar"
        ],
        "summary": "Edit an event",
        "description": "Edits apply to the whole series for recurring events; to change one day, or one day onward, use the occurrences PATCH endpoint, and to delete them the occurrences DELETE. Invitees are notified of the change. Same fields as create, all optional. `recurrence: null` stops the event recurring. A series keeps its own `exdates` whatever the request sends: each names a day that lives on as its own event or in the trash.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "content": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Plain-text notes, converted to HTML"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich notes (sanitized); wins over `content` when both are sent"
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "all_day": {
                    "type": "boolean"
                  },
                  "link": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri"
                  },
                  "circled": {
                    "type": "boolean"
                  },
                  "recurrence": {
                    "$ref": "#/components/schemas/CalendarRecurrence",
                    "description": "The repeat schedule; null means the event does not recur (clears it on update)"
                  },
                  "participant_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100,
                    "description": "Replaces the invitee set"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated event",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/{eventId}/ics": {
      "get": {
        "operationId": "downloadEventIcs",
        "tags": [
          "Calendar"
        ],
        "summary": "One event as an .ics file",
        "description": "\"Add to my calendar\": the event as a credential-authenticated iCalendar download (Content-Disposition: attachment). Recurring events export a year of occurrences. For an ongoing subscription mint a feed instead.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The iCalendar file",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string",
                  "description": "An RFC 5545 VCALENDAR document"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/{eventId}/occurrences/{date}": {
      "delete": {
        "operationId": "deleteEventOccurrence",
        "tags": [
          "Calendar"
        ],
        "summary": "Delete an occurrence of a recurring event",
        "description": "The trash prompt for recurring events (\"Only delete this event\" / \"Also delete all future events\"); recurring events only. What's deleted goes to the trash (`trashed_id`), and restoring it (`PUT /recordings/{trashed_id}/status/active`) puts the dates back into the series. Default: just this occurrence (`result: excluded`; the day is trashed as its own event). `?mode=future`: this and every later occurrence (`result: truncated`; the series ends the day before and the rest is trashed as one repeating event); from the first occurrence that trashes the whole series (`result: trashed`, `trashed_id` is the event itself). `date` is the occurrence's `occurrence_key`, the anchor-zone day key from the calendar listing; never derive it from `start` client-side. 404 when the series has no occurrence that day.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The occurrence's anchor-zone day key (YYYY-MM-DD)"
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "occurrence",
                "future"
              ],
              "default": "occurrence"
            },
            "description": "`future`: also delete all later occurrences"
          }
        ],
        "responses": {
          "200": {
            "description": "What happened to the series",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "result",
                    "trashed_id"
                  ],
                  "properties": {
                    "result": {
                      "type": "string",
                      "enum": [
                        "excluded",
                        "truncated",
                        "trashed"
                      ]
                    },
                    "trashed_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "What went to the trash; restoring it undoes the deletion"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateEventOccurrence",
        "tags": [
          "Calendar"
        ],
        "summary": "Edit an occurrence of a recurring event",
        "description": "The edit prompt for recurring events. Default (\"No, just change this event\"): the day splits off as its own event, linked to the series by `series_id` and `occurrence_key`, and takes the edit. `?mode=future` (\"Yes, change future occurrences too\"): the series ends the day before and a copy repeating from this day takes the edit; from the first occurrence the whole series is edited in place. Invitees hear about the change as with any edit, and a new date or time logs `rescheduled`. Returns the event that now carries the change, or the series unchanged when nothing changed. Same fields as editing an event, all optional. 404 when the series has no occurrence that day.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The occurrence's anchor-zone day key (YYYY-MM-DD)"
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "occurrence",
                "future"
              ],
              "default": "occurrence"
            },
            "description": "`future`: this and every later occurrence"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "content": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Plain-text notes, converted to HTML"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich notes (sanitized); wins over `content` when both are sent"
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "all_day": {
                    "type": "boolean"
                  },
                  "link": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri"
                  },
                  "circled": {
                    "type": "boolean"
                  },
                  "recurrence": {
                    "$ref": "#/components/schemas/CalendarRecurrence",
                    "description": "`?mode=future` only: how the event repeats from this day on (a single day repeats with its series). `exdates` is ignored"
                  },
                  "participant_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100,
                    "description": "Replaces the invitee set"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The event carrying the change",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/feeds": {
      "get": {
        "operationId": "listCalendarFeeds",
        "tags": [
          "Calendar"
        ],
        "summary": "List your iCal feeds",
        "description": "Your active (unrevoked) feeds. Each `url` is a capability URL: the token in the path is the only credential the public /feeds/{token}.ics endpoint checks, so treat it like a password. Revoked feeds return 404 there.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your feeds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CalendarFeed"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "createCalendarFeed",
        "tags": [
          "Calendar"
        ],
        "summary": "Mint an iCal feed",
        "description": "A token-authenticated read-only ICS feed honoring the filters, for subscribing an external calendar app. The returned `url` is the capability: anyone holding it can read the feed until you revoke it. Clients cannot mint feeds and receive 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Legacy single-project scope; cannot be combined with project_ids"
                  },
                  "project_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "format": "uuid"
                        },
                        {
                          "type": "string",
                          "const": "account"
                        }
                      ]
                    },
                    "description": "Selected projects and/or the account calendar. Omit for all calendars. Cannot be combined with project_id."
                  },
                  "starred_only": {
                    "type": "boolean",
                    "description": "Follow your starred projects as they change (the calendar's Starred pick). Cannot be combined with project_id or project_ids (422)."
                  },
                  "include_tasks": {
                    "type": "boolean",
                    "description": "Include due to-dos and cards"
                  },
                  "just": {
                    "type": "string",
                    "enum": [
                      "me",
                      "everyone"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The feed's capability URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "The public /feeds/{token}.ics URL; the token is the credential"
                    },
                    "project_ids": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Acknowledged selection, or null for all calendars"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/feeds/{feedId}": {
      "delete": {
        "operationId": "revokeCalendarFeed",
        "tags": [
          "Calendar"
        ],
        "summary": "Revoke a feed",
        "description": "Effective immediately: the feed's public URL starts returning 404. Only your own feeds are revocable.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "feedId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/calendar/parse": {
      "get": {
        "operationId": "parseCalendarInput",
        "tags": [
          "Calendar"
        ],
        "summary": "Parse natural-language event text",
        "description": "Prefill for the event form: \"Team call Friday at 10am\" becomes a title plus a start (and end, when a range was given). A suggestion the user confirms, never a silent commit. Relative dates and clock times mean the viewer's zone. When nothing date-like is found, the datetime fields come back null.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 10000
            },
            "description": "The text to parse"
          }
        ],
        "responses": {
          "200": {
            "description": "The parse suggestion",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarParseResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/categories": {
      "get": {
        "operationId": "listCategories",
        "tags": [
          "Recordings"
        ],
        "summary": "List message-board categories",
        "description": "Defaults are seeded per organization.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MessageCategory"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createCategory",
        "tags": [
          "Recordings"
        ],
        "summary": "Create a message-board category",
        "description": "Admin and up.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "icon": {
                    "type": "string",
                    "maxLength": 8,
                    "description": "An emoji"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageCategory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/categories/{categoryId}": {
      "patch": {
        "operationId": "updateCategory",
        "tags": [
          "Recordings"
        ],
        "summary": "Update a category",
        "description": "Admin and up.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "categoryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "icon": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 8
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated category",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageCategory"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteCategory",
        "tags": [
          "Recordings"
        ],
        "summary": "Delete a category",
        "description": "Admin and up; deleting clears the category from posts.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "categoryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/chats": {
      "get": {
        "operationId": "listChats",
        "tags": [
          "Chat"
        ],
        "summary": "List your direct chats",
        "description": "Your conversations, latest activity first, with participants and a preview of the last line. Direct chats have no project, never appear in activity, and are visible only to their participants. `?status=archived` lists archived ones: readable (and searchable) forever, but no new lines post into them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived"
              ],
              "default": "active"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DmSummary"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "findOrCreateChat",
        "tags": [
          "Chat"
        ],
        "summary": "Find or start a conversation",
        "description": "Finds the existing conversation with exactly these people or starts one; either way the `dm` recording comes back with 201. Your own id alone starts a chat with just yourself. You can message admins, owners, and anyone you share a project with. Lines are chat children: POST /recordings/{chat_id}/children with type chat_message; archive via PUT /recordings/{id}/status/archived (for everyone, cannot be undone).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "membership_ids"
                ],
                "properties": {
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 20
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The conversation (found or created)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/cheers/{cheerId}": {
      "delete": {
        "operationId": "removeCheer",
        "tags": [
          "Recordings"
        ],
        "summary": "Remove a cheer",
        "description": "Only whoever left the cheer, or an org admin or owner (403 otherwise; 404 when already gone). Works for recording cheers and event cheers alike.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "cheerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/companies": {
      "get": {
        "operationId": "listCompanies",
        "tags": [
          "People"
        ],
        "summary": "List companies",
        "description": "Companies with counts of active memberships. Removed memberships are excluded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's companies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Company"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createCompany",
        "tags": [
          "People"
        ],
        "summary": "Create a company",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "is_client": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new company",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "is_client"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "is_client": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/companies/{companyId}": {
      "patch": {
        "operationId": "updateCompany",
        "tags": [
          "People"
        ],
        "summary": "Update a company",
        "description": "Rename, toggle client status, or toggle `dms_disabled` (the per-company private-chat restriction: members drop from chat pickers, can't start DMs, and existing conversations freeze read-only both ways).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "companyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "is_client": {
                    "type": "boolean"
                  },
                  "dms_disabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated company",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "is_client",
                    "dms_disabled"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "is_client": {
                      "type": "boolean"
                    },
                    "dms_disabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteCompany",
        "tags": [
          "People"
        ],
        "summary": "Delete a company",
        "description": "People keep their membership; they just lose the company association.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "companyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/embeds": {
      "post": {
        "operationId": "createEmbed",
        "tags": [
          "Files"
        ],
        "summary": "Turn a URL into sanctioned embed markup",
        "description": "Server-generated embed markup for the allowlist: YouTube, Vimeo, Loom, Spotify, X, Flickr, Imgur, SoundCloud, and direct `.gif` URLs. Non-allowlisted URLs return `{html: null}`: keep a plain link. Client-supplied iframe HTML is never accepted anywhere; the returned markup also round-trips the rich-text sanitizer.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The markup, or null for a non-allowlisted URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "html"
                  ],
                  "properties": {
                    "html": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Sanitized figure/iframe markup to insert"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/events/{eventId}/cheers": {
      "get": {
        "operationId": "listEventCheers",
        "tags": [
          "Recordings"
        ],
        "summary": "List cheers on a change-log event",
        "description": "Cheers on one row of a recording's change log. The per-recording events listing already inlines the same shape.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cheers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EventCheer"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createEventCheer",
        "tags": [
          "Recordings"
        ],
        "summary": "Cheer a change-log event",
        "description": "Up to 16 characters of text or emoji. Pings the event's actor through their `notify_cheers` channel, bundled into one notification per recording within 3 hours. An exact duplicate returns 200 `{already_cheered: true}` instead of 201.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already cheered with exactly this content",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "already_cheered"
                  ],
                  "properties": {
                    "already_cheered": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "The added cheer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "content"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "content": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/folders": {
      "get": {
        "operationId": "listProjectFolders",
        "tags": [
          "Files"
        ],
        "summary": "List project folders",
        "description": "Account-wide home-screen groupings of projects. Move a project in or out via PATCH /projects/{id} with `folder_id` (or null).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The folders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectFolder"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createProjectFolder",
        "tags": [
          "Files"
        ],
        "summary": "Create a project folder",
        "description": "Any non-client member can manage folders; clients get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectFolder"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/folders/{folderId}": {
      "patch": {
        "operationId": "updateProjectFolder",
        "tags": [
          "Files"
        ],
        "summary": "Rename, recolor, or reorder a folder",
        "description": "Any non-client member; clients get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "folderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "position": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated folder",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectFolder"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteProjectFolder",
        "tags": [
          "Files"
        ],
        "summary": "Delete a project folder",
        "description": "The folder's projects drop back to the top level. Any non-client member; clients get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "folderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/groups": {
      "get": {
        "operationId": "listGroups",
        "tags": [
          "People"
        ],
        "summary": "List groups",
        "description": "Groups bundle non-client people for @-mentions and pickers. Removed memberships are excluded; groups with no active members remain in the list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's groups with their members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PeopleGroup"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createGroup",
        "tags": [
          "People"
        ],
        "summary": "Create a group",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/groups/{groupId}": {
      "patch": {
        "operationId": "updateGroup",
        "tags": [
          "People"
        ],
        "summary": "Replace a group's members",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "membership_ids"
                ],
                "properties": {
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Replaces the member set"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteGroup",
        "tags": [
          "People"
        ],
        "summary": "Delete a group",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "groupId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/merge-people": {
      "get": {
        "operationId": "listMerges",
        "tags": [
          "Organizations"
        ],
        "summary": "List recent people merges",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Recent merges",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PersonMerge"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "mergePeople",
        "tags": [
          "Organizations"
        ],
        "summary": "Merge two people",
        "description": "Irreversible dedupe: the winner inherits the loser's assignments, subscriptions, project access, and authorship.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "winner_id",
                  "loser_id"
                ],
                "properties": {
                  "winner_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "loser_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/agent/inbox": {
      "get": {
        "operationId": "listAgentRequests",
        "tags": [
          "Agents"
        ],
        "summary": "Read the agent request inbox",
        "description": "Agent identities only. Oldest first, up to 100 requests from the last 30 days. Only active human operators can create requests. Exact source pointers are independent of the bundled human tray. Superseded/inaccessible pending work is dismissed. Terminal/running rows whose content is no longer accessible contain only id, status and result.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "running",
                "completed",
                "failed",
                "interrupted"
              ],
              "default": "pending"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentRequest"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/agent/inbox/{requestId}": {
      "post": {
        "operationId": "settleAgentRequest",
        "tags": [
          "Agents"
        ],
        "summary": "Claim or settle a request",
        "description": "Requires the active runtime lease. Claim changes pending to running atomically; another claim fails. Completed/failed settles only work held by this runtime. Posting a reply is a separate content operation, attributed to the agent.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AgentRequest"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "status"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "runtime_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "claim",
                      "completed",
                      "failed"
                    ]
                  },
                  "result": {
                    "type": "string",
                    "maxLength": 2000,
                    "default": ""
                  }
                },
                "required": [
                  "runtime_id",
                  "status"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/agent/session": {
      "post": {
        "operationId": "renewAgentSession",
        "tags": [
          "Agents"
        ],
        "summary": "Start, renew or stop the listening lease",
        "description": "Agent identities only. Renew within 60 seconds. A different unexpired runtime is refused with agent_busy. Stopping or replacing an expired session interrupts unfinished requests.",
        "parameters": [
          {
            "name": "orgSlug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "runtime_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "lease_seconds": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "runtime_id",
                    "lease_seconds"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "runtime_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "stop": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "runtime_id"
                ]
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/assignments": {
      "get": {
        "operationId": "getMyAssignments",
        "tags": [
          "Search & reports"
        ],
        "summary": "Your open assignments",
        "description": "Open to-dos, cards and card steps assigned to you across projects, grouped into `priorities` (your Up Next list, in your order; items carry `priority_id`) and `non_priorities`. Card steps are normalized under their parent card as `children`: the card is pulled in even when only a step is assigned, and a prioritized step surfaces its card. Top-level items carry `assigned_to_me`, false only for a pulled-in parent card, which is not your assignment and cannot go Up Next. Schedule events you are invited to are not assignments: they live on /my/events and /my/do-today.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your open assignments, grouped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "priorities",
                    "non_priorities"
                  ],
                  "properties": {
                    "priorities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GroupedAssignment"
                      }
                    },
                    "non_priorities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GroupedAssignment"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/assignments/completed": {
      "get": {
        "operationId": "listCompletedAssignments",
        "tags": [
          "Search & reports"
        ],
        "summary": "Your completed assignments",
        "description": "Completed to-dos, cards and card steps, newest first; assignments on archived or trashed recordings are excluded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Completed assignments, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AssignmentRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/assignments/due": {
      "get": {
        "operationId": "listDueAssignments",
        "tags": [
          "Search & reports"
        ],
        "summary": "Your dated assignments by due window",
        "description": "Dated open to-dos, cards and card steps in one due window. `overdue` is the default; `due_later_this_week` runs through this Sunday and `due_next_week` covers next Monday to Sunday, computed in your time zone.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "overdue",
                "due_today",
                "due_tomorrow",
                "due_later_this_week",
                "due_next_week",
                "due_later"
              ],
              "default": "overdue"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dated open assignments in the window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AssignmentRow"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope: the message lists the valid options",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/assignments/priorities": {
      "get": {
        "operationId": "listUpNext",
        "tags": [
          "Search & reports"
        ],
        "summary": "Your Up Next list",
        "description": "Your curated priority list, in your order; every row carries `priority_id`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Up Next, in your order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UpNextItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "addUpNext",
        "tags": [
          "Search & reports"
        ],
        "summary": "Add an assignment to Up Next",
        "description": "Adds one of your own open assignments (a to-do, card or card step) to the bottom of Up Next (idempotent). Anything else, a schedule event included, is a 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "recording_id"
                ],
                "properties": {
                  "recording_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added (or already there)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/assignments/priorities/{recordingId}": {
      "patch": {
        "operationId": "moveUpNext",
        "tags": [
          "Search & reports"
        ],
        "summary": "Reorder within Up Next",
        "description": "Swaps the item with its neighbor in the direction of `move`. Items not in your Up Next list are a 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "move"
                ],
                "properties": {
                  "move": {
                    "type": "string",
                    "enum": [
                      "up",
                      "down"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reordered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "removeUpNext",
        "tags": [
          "Search & reports"
        ],
        "summary": "Drop an item out of Up Next",
        "description": "The item returns to `non_priorities` on the assignments list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/bookmarks": {
      "get": {
        "operationId": "listBookmarks",
        "tags": [
          "Me"
        ],
        "summary": "Your bookmarks",
        "description": "Personal saved links to recordings, newest first. Only bookmarks the caller can still open are listed: one in a project they left, or on an item no longer shared with them, reappears if access returns.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your bookmarks, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BookmarkRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "addBookmark",
        "tags": [
          "Me"
        ],
        "summary": "Bookmark a recording",
        "description": "Idempotent; the recording must be one you can see.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "recording_id"
                ],
                "properties": {
                  "recording_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bookmarked (or already was)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/bookmarks/{recordingId}": {
      "delete": {
        "operationId": "removeBookmark",
        "tags": [
          "Me"
        ],
        "summary": "Remove a bookmark",
        "description": "Idempotent: removing a bookmark that is not there still succeeds.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/cheers": {
      "get": {
        "operationId": "listMyCheers",
        "tags": [
          "Me"
        ],
        "summary": "Cheers you have received and given",
        "description": "`{received, given}`, each newest first. Cheer notifications are their own channel, gated solely by the `notify_cheers` preference and never by the notify scope. `since` restricts both lists to cheers created after that instant. Each row includes `cheerer_membership_id`. Cheers do not create named-agent requests.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only cheers created after this instant"
          }
        ],
        "responses": {
          "200": {
            "description": "Received and given cheers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "received",
                    "given"
                  ],
                  "properties": {
                    "received": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MyCheerRow"
                      }
                    },
                    "given": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MyCheerRow"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/do-today": {
      "get": {
        "operationId": "getDoToday",
        "tags": [
          "Me"
        ],
        "summary": "Today's events and tasks",
        "description": "`{events, tasks}` for today, side by side: your events plus your due assignments. The \"today\" window is computed in your resolved time zone, and each item carries `kind` (`event` or `task`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Today's items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events",
                    "tasks"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DoTodayItem"
                      }
                    },
                    "tasks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DoTodayItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/drafts": {
      "get": {
        "operationId": "listMyDrafts",
        "tags": [
          "Me"
        ],
        "summary": "Your unpublished drafts",
        "description": "Drafts across the org. Only ever returns the caller's own: drafts are shareable by direct link to non-client project members, but they stay unlisted everywhere, and every draft write is author-only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your drafts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Recording"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/events": {
      "get": {
        "operationId": "listMyEvents",
        "tags": [
          "Me"
        ],
        "summary": "Your events, next 7 days",
        "description": "Your upcoming events across all projects, recurring occurrences expanded. All-day events use the floating `T00:00:00Z` convention.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Events in the next 7 days, in start order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MyEventRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/events/soon": {
      "get": {
        "operationId": "getEventStartingSoon",
        "tags": [
          "Me"
        ],
        "summary": "Your next event starting soon",
        "description": "The next timed event starting within about 20 minutes (the countdown banner); `{event: null}` when nothing is imminent. `href` is the web app path to the event, carrying `?occurrence=` for a recurring occurrence.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The imminent event, or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "event"
                  ],
                  "properties": {
                    "event": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "required": [
                        "id",
                        "title",
                        "starts_at",
                        "href"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "starts_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "link": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The event's join link, when set"
                        },
                        "href": {
                          "type": "string",
                          "description": "Web app path to the event"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/getting-started": {
      "get": {
        "operationId": "getMyGettingStarted",
        "tags": [
          "Notifications"
        ],
        "summary": "Getting-started guide catalog and notification preference",
        "description": "Evergreen guides this role may open (none for clients; the invitation guide for owners and admins). Fetch message content using the existing bulletin detail endpoint. Reading there acknowledges the guide; the catalog does not mark anything read.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Getting-started guidance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "state",
                    "stopped",
                    "messages"
                  ],
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "active",
                        "opted_out",
                        "ended",
                        "not_enrolled"
                      ],
                      "description": "Where the person's sequence stands. active: guides may still be delivered. opted_out: the person stopped them. ended: the seven-day window passed. not_enrolled: no sequence (a client-only account, or an account that predates the feature)."
                    },
                    "stopped": {
                      "type": "boolean",
                      "description": "True whenever nothing more will be delivered (state is not active). Offer the stop control only when false."
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "bulletin_id",
                          "title",
                          "excerpt"
                        ],
                        "properties": {
                          "bulletin_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "title": {
                            "type": "string"
                          },
                          "excerpt": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "patch": {
        "operationId": "stopMyGettingStarted",
        "tags": [
          "Notifications"
        ],
        "summary": "Stop getting-started notifications",
        "description": "Stops the sequence for this person across workspaces and marks its current-workspace messages read. Work notifications and email preferences are unchanged. The guides remain readable. Idempotent, and accepted in any state.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "stopped"
                ],
                "properties": {
                  "stopped": {
                    "type": "boolean",
                    "enum": [
                      true
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Getting-started guidance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "state",
                    "stopped"
                  ],
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "opted_out"
                      ]
                    },
                    "stopped": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "description": "Only stopped: true is accepted"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/note": {
      "get": {
        "operationId": "getMyNote",
        "tags": [
          "Me"
        ],
        "summary": "Your notes",
        "description": "My Notes: one private rich-text scratchpad per person, created empty the first time it is read. Only its creator can ever read it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your pad",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "patch": {
        "operationId": "saveMyNote",
        "tags": [
          "Me"
        ],
        "summary": "Save your notes",
        "description": "The autosave. Saving what is already stored is a no-op that leaves `updated_at` alone. Pass `base_updated_at` to refuse a stale write: a 409 carries the current pad under `error.note` and writes nothing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MyNoteSave"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved pad",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "409": {
            "description": "The pad changed since `base_updated_at`; nothing was written",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message",
                        "note"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "stale"
                          ]
                        },
                        "message": {
                          "type": "string"
                        },
                        "note": {
                          "$ref": "#/components/schemas/MyNote"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/note/restore": {
      "post": {
        "operationId": "restoreMyNote",
        "tags": [
          "Me"
        ],
        "summary": "Restore your previous notes",
        "description": "Swaps the pad with its restore point (`previous_version`), so restoring again undoes the restore. 404 when there is no earlier version yet.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The restored pad",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/notes": {
      "get": {
        "operationId": "listMyNotes",
        "deprecated": true,
        "tags": [
          "Me"
        ],
        "summary": "Your notes as a one-item list (deprecated)",
        "description": "Legacy note list, kept only for the 1.5.1 App Store app and removed with the native app's My Notes screen (parity-followups-sweep row 48). Use `/my/note`. Returns `[pad]`: the one scratchpad, created empty if needed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The pad, as a list of one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "maxItems": 1,
                  "items": {
                    "$ref": "#/components/schemas/MyNote"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "createMyNote",
        "deprecated": true,
        "tags": [
          "Me"
        ],
        "summary": "Append to your notes (deprecated)",
        "description": "Legacy note list, kept only for the 1.5.1 App Store app and removed with the native app's My Notes screen (parity-followups-sweep row 48). Use `/my/note`. There is no second note to create: the jot is appended to the end of the pad, `title` as a heading over it, and the pad comes back as 201.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Becomes a heading over the appended body"
                  },
                  "content": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50000,
                    "description": "Plain text; converted to HTML"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich HTML, sanitized; wins over content"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The pad, with the jot appended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/notes/{noteId}": {
      "patch": {
        "operationId": "updateMyNote",
        "deprecated": true,
        "tags": [
          "Me"
        ],
        "summary": "Save your notes by id (deprecated)",
        "description": "Legacy note list, kept only for the 1.5.1 App Store app and removed with the native app's My Notes screen (parity-followups-sweep row 48). Use `/my/note`. Only the pad's own id resolves; anything else is a 404. The pad has no title, so `title` is accepted and dropped; `content_html` wins over `content` and replaces the whole pad, with no stale check.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "noteId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000,
                    "description": "Accepted and dropped"
                  },
                  "content": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50000,
                    "description": "Plain text; converted to HTML"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich HTML, sanitized; wins over content"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved pad",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteMyNote",
        "deprecated": true,
        "tags": [
          "Me"
        ],
        "summary": "Clear your notes (deprecated)",
        "description": "Legacy note list, kept only for the 1.5.1 App Store app and removed with the native app's My Notes screen (parity-followups-sweep row 48). Use `/my/note`. Clears the pad (its content becomes null; the restore point keeps what it held). The row itself stays: the pad is one row per person for good.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "noteId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cleared",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/notifications": {
      "get": {
        "operationId": "listNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "Your notification tray",
        "description": "`{unread_count, notifications}`, newest first. Event and due-date reminders sweep lazily on every read (the count-only arm included) with DB-level dedupe, so reminder rows land exactly once even under concurrent reads. Rows carry `bulletin_id` for platform announcements (action `announced`, actor-less) and `bundle_count` for chat rows: lines folded into one entry while it stayed unread. Project-chat rows use action `chatted` for the push-worthy idle-edge delivery and `chat_activity` for the quiet bundled tray row, both pointing at the room recording; visiting the room marks them read. **Cursor reads:** pass `since` (the last row's `created_at`) and `after` (its id) to walk forward, oldest first; the response then carries `next_cursor` to pass next time. Rows carry the actor's membership id and role, and for agent memberships the server's trust verdicts: `from_operator` (the actor is one of the agent's operators) and `directive` (a mention or assignment from someone allowed to direct it). The legacy agent fields are retained for compatibility. Bundled notifications are context, not executable requests. Named-agent listeners use `/my/agent/inbox` and `/my/agent/session`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "unread",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "true: unread rows only"
          },
          {
            "name": "count_only",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "true: return {unread_count} alone (the mobile tab badge's poll; the reminder sweeps still run)"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Cursor: only rows created after this instant; switches the order to oldest first"
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Cursor tie-breaker: the id of the last row read (pair with since)"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list of actions to include, e.g. mentioned,assigned"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size (default 50)"
          },
          {
            "name": "presence",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "true: renew the caller's agent presence lease (agents only; a no-op for people)"
          }
        ],
        "responses": {
          "200": {
            "description": "The tray (notifications omitted with count_only=true)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "unread_count"
                  ],
                  "properties": {
                    "unread_count": {
                      "type": "integer"
                    },
                    "notifications": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NotificationRow"
                      }
                    },
                    "next_cursor": {
                      "type": "object",
                      "description": "Present on cursor reads only: pass these as since/after on the next call",
                      "required": [
                        "since",
                        "after"
                      ],
                      "properties": {
                        "since": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "after": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "put": {
        "operationId": "markAllNotificationsRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark all notifications read",
        "description": "Marks every notification in this org read.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "read": {
                    "type": "boolean",
                    "enum": [
                      true
                    ]
                  }
                },
                "description": "Optional; {read: true} is the only accepted body"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "All read",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/notifications/{notificationId}": {
      "patch": {
        "operationId": "markNotificationRead",
        "tags": [
          "Notifications"
        ],
        "summary": "Mark one notification read or unread",
        "description": "`{read: false}` returns it to the tray's \"New for you\" section. Unknown ids are a quiet no-op.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "notificationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "read"
                ],
                "properties": {
                  "read": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/notifications/stream": {
      "get": {
        "operationId": "streamNotifications",
        "tags": [
          "Notifications"
        ],
        "summary": "Stream your notifications (Server-Sent Events)",
        "description": "Personal notifications as a `text/event-stream` that delivers each new notification row as it lands (`event: notification`, `id:` = the row id, `data:` = the same JSON as the tray), sends `: ping` every 15 seconds, and ends with `event: reconnect` after ten minutes; reconnect with `since`/`after` (or `Last-Event-ID`) to continue. Opens with `event: ready` carrying the cursor. Without a cursor the stream starts now, never replaying history. Named-agent listeners use the separate `/my/agent/inbox` and `/my/agent/session` endpoints.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Resume cursor (the last row's created_at)"
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Resume cursor tie-breaker (the last row's id)"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list of actions to include"
          }
        ],
        "responses": {
          "200": {
            "description": "An open event stream",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/snoozes": {
      "get": {
        "operationId": "listSnoozes",
        "tags": [
          "Notifications"
        ],
        "summary": "Your snoozes (Remind me)",
        "description": "`{pending_count, due, pending}`: `due` rows' resurface time has passed (the sidebar's \"Back for you\" section), `pending` rows are still waiting (the \"N waiting\" list). Trashed content is excluded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Due and pending snoozes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "pending_count",
                    "due",
                    "pending"
                  ],
                  "properties": {
                    "pending_count": {
                      "type": "integer"
                    },
                    "due": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SnoozeRow"
                      }
                    },
                    "pending": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SnoozeRow"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/snoozes/{recordingId}": {
      "post": {
        "operationId": "snoozeRecording",
        "tags": [
          "Notifications"
        ],
        "summary": "Snooze a recording",
        "description": "Snoozes the recording until `resurface_at`, when it lands in the sidebar's \"Back for you\" section. Upserts your existing snooze; the recording must be one you can see.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resurface_at"
                ],
                "properties": {
                  "resurface_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO 8601, local or with offset"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "A note to future you, shown when it resurfaces"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Snoozed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "dismissSnooze",
        "tags": [
          "Notifications"
        ],
        "summary": "Dismiss a snooze",
        "description": "Idempotent: dismissing a snooze that is not there still succeeds.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/timesheet": {
      "get": {
        "operationId": "getMyTimesheet",
        "tags": [
          "Timesheets"
        ],
        "summary": "Read a week of the weekly timesheet",
        "description": "One person's week, Mon to Sun or from whatever day the org's week starts: its rows (the ones added that week, plus one for every item, event day or project the week's hours are on), the Absence rows (every active absence type, and a removed one while it has hours that week), the week's counted entries and what the caller may change. Each week starts fresh: nothing carries over. Time in projects the caller can't see stays out. Members read their own week; owners and admins anyone's on the team (`person_id`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "week",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Any day in the week; default today in the caller's zone. The week runs from the org's week start"
          },
          {
            "name": "person_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Whose week (a membership id); default the caller. Owners and admins only"
          }
        ],
        "responses": {
          "200": {
            "description": "The week",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetWeek"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/timesheet/absences": {
      "post": {
        "operationId": "createTimesheetAbsence",
        "tags": [
          "Timesheets"
        ],
        "summary": "Log absence",
        "description": "Time off (vacation, sick leave, …) belongs to the org, not a project: the entry has no project and no parent. Only the person, owners and admins see it; it never shows on a project timesheet.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "absence_type_id",
                  "date",
                  "hours"
                ],
                "properties": {
                  "absence_type_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "An active absence type"
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Combined regular and overtime hours, like 1.5 or 1:30. Must be positive; at most 24 hours a person a day across entries."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000,
                    "description": "Notes"
                  },
                  "person_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose time; default the caller. Owners and admins may name anyone on the team"
                  },
                  "overtime_hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Overtime portion of the combined hours, like 1.5 or 1:30. Must be between zero and hours. Defaults to zero on create; omitted on update, preserves existing overtime up to the new total."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetEntry"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation (code `invalid`: bad hours, a bad day, a removed absence type) or the 24-hour daily cap (code `daily_cap`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/timesheet/rows": {
      "post": {
        "operationId": "addMyTimesheetRow",
        "tags": [
          "Timesheets"
        ],
        "summary": "Add a row to a week",
        "description": "A row the week keeps without hours: a project, with an item in it or not. The same guards as logging time there (Timesheet on, the item active, the person on the project). A row the week already has comes back as it is.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "project_id"
                ],
                "properties": {
                  "week": {
                    "type": "string",
                    "format": "date",
                    "description": "Any day in the week; default today in the caller's zone"
                  },
                  "project_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "A project with its Timesheet on"
                  },
                  "recording_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The item (a to-do, message, document, file, card or event in the project), or the project's timesheet; omit for time on the project itself"
                  },
                  "occurrence": {
                    "type": "string",
                    "format": "date",
                    "description": "Required for a repeating event: the day"
                  },
                  "person_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose week; default the caller. Owners and admins may name anyone on the project's team"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The row",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetWeekRow"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation (code `invalid`): a bad day, a repeating event without `occurrence`, a person who isn't a non-client member of the project, an archived or trashed item",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeMyTimesheetRow",
        "tags": [
          "Timesheets"
        ],
        "summary": "Remove a row from a week",
        "description": "Removes the row and, with `delete_entries=true`, its entries that week, permanently. Entries on other days of the event or in other weeks stay.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recording_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The row's `recording_id`",
            "required": true
          },
          {
            "name": "occurrence",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The row's `occurrence_date`, when it has one"
          },
          {
            "name": "week",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Any day in the week; default today in the caller's zone. The week runs from the org's week start"
          },
          {
            "name": "person_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Whose week; default the caller. Owners and admins only"
          },
          {
            "name": "delete_entries",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "Required as `true` when the row has hours that week: they're deleted for good"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "deleted_entries"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "deleted_entries": {
                      "type": "integer",
                      "description": "How many entries went with it"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The row has hours that week and `delete_entries` isn't `true` (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/timesheet/weeks/{weekStart}/submit": {
      "post": {
        "operationId": "submitMyTimesheetWeek",
        "tags": [
          "Timesheets"
        ],
        "summary": "Submit a week for approval",
        "description": "While approvals are on: records the week that starts on `weekStart` with its hours as they are now. A changed or rejected week is resubmitted the same way; one already submitted and unchanged comes back as it is. Nothing locks: the week stays editable, and a later change reads `changed`. Notifies no one; the week appears on the approvals page. Members submit their own week, owners and admins anyone's on the team (`person_id`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "weekStart",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The week's first day, by the org's week start"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "person_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose week (a membership id); default the caller. Owners and admins only"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The week's approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetWeekApproval"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Approvals are off (code `approvals_off`), or the week is already approved (code `week_state`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`weekStart` isn't a day the org's weeks start on (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/my/ui-state": {
      "get": {
        "operationId": "getUiState",
        "tags": [
          "Me"
        ],
        "summary": "Your cross-device UI memory",
        "description": "A free-form JSON object of personal UI state, e.g. `{collapsedColumns: {<projectId>: [columnIds]}}` for the saved-just-for-you card-table column collapse, keyed by project id, and `{docsView: {<folderId>: \"grid\"|\"list\"}}` for the Docs & Files grid/list choice, remembered per folder (the tool's root container carries the top level's choice, which the project page's Docs & Files card follows; a folder with no entry opens the way its nearest ancestor with one does, else `list`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Your UI state ({} until you save some)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "put": {
        "operationId": "updateUiState",
        "tags": [
          "Me"
        ],
        "summary": "Merge into your UI memory",
        "description": "A shallow top-level merge: each key you send replaces that key wholesale, so send `collapsedColumns` and `docsView` complete. Returns the merged state.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merged state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people": {
      "get": {
        "operationId": "listPeople",
        "tags": [
          "People"
        ],
        "summary": "List the organization's people",
        "description": "Active members with role, title, company, out-of-office dates, and whether their email address is bouncing. Soft-removed people are excluded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization's people",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Person"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "invitePerson",
        "tags": [
          "People"
        ],
        "summary": "Invite someone",
        "description": "Admins and up; owner invites require an owner. `note` rides the invitation email as plain text, and a note with a link, web address or email address is refused with 422 (invalid) before anything is written or sent; `project_ids` are granted on accept. The email carries a decline link that stamps the invite declined and notifies the inviter. Plan seat limits apply. One invitation per address per workspace: inviting an address that already has a pending invitation folds into it. New `project_ids` pool onto that invitation and go out as the 'added you to' follow-up on its original join link (201); with nothing new to grant the call answers 403 (already_invited) and sends nothing, resend is the nudge. A declined or expired invitation is revived and re-sent on the same link, under the reminder cooldown (403 invitation_send_cooldown inside an hour of the last invitation email to that address). The invitation and email job are committed together; email delivery runs after commit and retries transient failures. Success confirms queuing, not provider delivery. A global email pause returns 503 (email_paused) before changing the invitation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "role"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "role": {
                    "$ref": "#/components/schemas/OrgRole"
                  },
                  "company_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Plain text, included in the invitation email. A note with a link, a web address (a bare domain such as example.com counts) or an email address is refused with 422 (invalid)."
                  },
                  "project_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100,
                    "description": "Projects granted when the invite is accepted"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation queued for delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invited"
                  ],
                  "properties": {
                    "invited": {
                      "type": "string",
                      "format": "email"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "503": {
            "description": "Email delivery is temporarily paused (code email_paused). Retry after the indicated delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Retry delay in seconds"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/{membershipId}": {
      "get": {
        "operationId": "getPerson",
        "tags": [
          "People"
        ],
        "summary": "One person's org profile",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The person",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Person"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updatePerson",
        "tags": [
          "People"
        ],
        "summary": "Change a person's role, title, or company",
        "description": "Admins and up. Last-owner protections apply. `role: \"client\"` also strips group memberships and access to projects that don't work with clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "$ref": "#/components/schemas/OrgRole"
                  },
                  "company_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "removePerson",
        "tags": [
          "People"
        ],
        "summary": "Remove someone from the org (or leave)",
        "description": "Soft removal: contributions keep their name, and re-inviting the email reactivates the membership. Removing yourself is leaving; leaving accepts an optional `{note}` body (2000 chars max) emailed to the owners with the departure notice. Admin removes send no body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Plain text. Leaving only; emailed to the owners"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/{membershipId}/events": {
      "get": {
        "operationId": "listPersonEvents",
        "tags": [
          "People"
        ],
        "summary": "One person's activity trail",
        "description": "The Someone's-activity report: the person plus their events, newest first. Team only; clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "The person and their events; while more exist, `Link: rel=\"next\"` names the next page",
            "headers": {
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "person",
                    "events"
                  ],
                  "properties": {
                    "person": {
                      "type": "object",
                      "required": [
                        "id",
                        "name"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Membership id"
                        },
                        "name": {
                          "type": "string"
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "out_of_office": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "start": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date"
                            },
                            "end": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date"
                            }
                          }
                        }
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PersonEvent"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/{membershipId}/out_of_office": {
      "get": {
        "operationId": "getOutOfOffice",
        "tags": [
          "People"
        ],
        "summary": "Someone's out-of-office state",
        "description": "Dates are null while out-of-office is off.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The out-of-office representation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfOffice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "setOutOfOffice",
        "tags": [
          "People"
        ],
        "summary": "Enable or replace out-of-office",
        "description": "Sets the date range, replacing any existing one. Writes are self-only unless the caller is an owner or admin.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "out_of_office"
                ],
                "properties": {
                  "out_of_office": {
                    "type": "object",
                    "required": [
                      "start_date",
                      "end_date"
                    ],
                    "properties": {
                      "start_date": {
                        "type": "string",
                        "format": "date"
                      },
                      "end_date": {
                        "type": "string",
                        "format": "date",
                        "description": "On or after start_date"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fresh representation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutOfOffice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "disableOutOfOffice",
        "tags": [
          "People"
        ],
        "summary": "Disable out-of-office",
        "description": "Idempotent. Same self-or-admin rule as enabling.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disabled"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/{membershipId}/projects": {
      "get": {
        "operationId": "getPersonProjects",
        "tags": [
          "People"
        ],
        "summary": "A person's project access",
        "description": "The admin Change-access surface (admin and up): every project the person can reach. `has_row` marks an explicit grant; non-clients are on all-access projects implicitly, and client targets see only clients-enabled projects. Removed people answer 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The person's reachable projects",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonProjectAccess"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setPersonProjects",
        "tags": [
          "People"
        ],
        "summary": "Replace a person's explicit project access",
        "description": "Replaces the explicit grants (admin and up): the membership mirror of the invitation /projects route.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "project_ids"
                ],
                "properties": {
                  "project_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fresh access representation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonProjectAccess"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/invitations": {
      "get": {
        "operationId": "listInvitations",
        "tags": [
          "People"
        ],
        "summary": "List pending invitations",
        "description": "Pending invitations, oldest first (admin and up). Declined invites stay listed (a resend revives them); accepted ones became memberships and drop out. `email_status` is the delivery ledger's word on the latest invite email; `join_url` is the same link the email button carries, for sharing over another channel when email fails.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending invitations, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Invitation"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/invitations/{invitationId}": {
      "delete": {
        "operationId": "revokeInvitation",
        "tags": [
          "People"
        ],
        "summary": "Revoke a pending invitation",
        "description": "Hard delete (admin and up): the emailed link dies. Accepted or missing invitations answer 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/invitations/{invitationId}/projects": {
      "get": {
        "operationId": "getInvitationProjects",
        "tags": [
          "People"
        ],
        "summary": "A pending invitee's project access",
        "description": "What the invitee will be able to reach, and which projects the invitation already grants (admin and up). Client invites see only clients-enabled projects.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invitation's reachable projects with per-row grants",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationProjectAccess"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setInvitationProjects",
        "tags": [
          "People"
        ],
        "summary": "Replace a pending invitee's project grants",
        "description": "Replaces the grants-on-accept (admin and up). Newly added projects send the invitee an added-you-to-that-project email whose join link lands them straight in that project after accepting.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "project_ids"
                ],
                "properties": {
                  "project_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fresh access representation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationProjectAccess"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/invitations/{invitationId}/resend": {
      "post": {
        "operationId": "resendInvitation",
        "tags": [
          "People"
        ],
        "summary": "Resend an invitation",
        "description": "Nudge a pending invitee: fresh email, same still-valid token (admin and up). Also revives a declined invitation so it can be accepted again (seat limits apply to the revive). Reminders rest an hour: inside an hour of the last invitation email to that address the call answers 403 (invitation_send_cooldown) and sends nothing; the join link on the invitation stays the fallback. The invitation and email job are committed together; email delivery runs after commit and retries transient failures. Success confirms queuing, not provider delivery. A global email pause returns 503 (email_paused) before changing the invitation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation reminder queued for delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "description": "Email delivery is temporarily paused (code email_paused). Retry after the indicated delay.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Retry delay in seconds"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/people/invite-link": {
      "get": {
        "operationId": "getInviteLink",
        "tags": [
          "People"
        ],
        "summary": "Get the shareable invite link",
        "description": "The org's auto-join link (admin and up): anyone holding the URL joins as a team member. Member seat limits are checked at join time, and the joiner's address is not auto-verified (a URL proves no mailbox).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The link's state; url is null while disabled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "enableInviteLink",
        "tags": [
          "People"
        ],
        "summary": "Mint or re-enable the invite link",
        "description": "An empty body means make sure the link is on. `{\"rotate\": true}` swaps in a fresh token so the old URL stops working (admin and up).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rotate": {
                    "type": "boolean",
                    "description": "Swap the token; the old URL dies"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enabled link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "disableInviteLink",
        "tags": [
          "People"
        ],
        "summary": "Turn the invite link off",
        "description": "The off state sticks until a POST turns it back on (admin and up).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The disabled link (enabled false, url null)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Active projects you can access, starred first then by name. Rows carry `starred`, `folder_id`, and `is_sample` (true only on the seeded Getting Started project, which never counts against the plan's project limit). `?status` lists archived or trashed instead. `?include=members` is the home listing: active rows also carry `members` (first 5 active project members by name; all-access projects union the org's active non-client people with explicitly added agents and clients) and `member_count`. `?scope=all` is the owner-only trusted list of every project with a `joined` flag; admins and members receive 403. Access model: nobody, owners and admins included, has implicit access to a non-all-access project's content; membership or `all_access` is required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed"
              ]
            },
            "description": "Defaults to active"
          },
          {
            "name": "include",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "members"
              ]
            },
            "description": "members: the home listing's avatar-stack fields"
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all"
              ]
            },
            "description": "all: owner-only list of every project with a joined flag"
          }
        ],
        "responses": {
          "200": {
            "description": "Projects, starred first then by name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "description": "Auto-starred for the creator. `all_access` defaults to true. The plan's project cap applies; the seeded sample project never counts toward it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 150,
                    "description": "Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "all_access": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "Projects"
        ],
        "summary": "Get a project",
        "description": "The project plus its `tools`, each with its `container_id` (content is created under that container recording). `dock` is a deprecated alias of `tools` during the vocabulary transition. `timesheet_enabled` is the project's Timesheet switch; clients never receive it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The project with its tools",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateProject",
        "tags": [
          "Projects"
        ],
        "summary": "Update a project",
        "description": "`color` is a preset card color; anything outside the enum is a 400. `starts_on` and `ends_on` travel together: set both or null both, and `ends_on` must not precede `starts_on` (violations are a 400). `show_activity` toggles the project's activity rollups. `clients_enabled: false` reverts every recording in the project to team-only and drops client access (Work with clients); `client_company_name` is the label shown on the project. Move the project in or out of a home-screen folder with `folder_id` (or null). `timesheet_enabled` switches the project's Timesheet (a project-details edit: never clients, and \"Restrict who can edit project details\" applies); the response then carries it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 150,
                    "description": "Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted. Resending the current name is always accepted, even one set before this rule."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "all_access": {
                    "type": "boolean"
                  },
                  "folder_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "yellow",
                      "orange",
                      "coral",
                      "pink",
                      "purple",
                      "blue",
                      "green",
                      "tan",
                      "gray",
                      null
                    ]
                  },
                  "starts_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "ends_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "show_activity": {
                    "type": "boolean"
                  },
                  "clients_enabled": {
                    "type": "boolean"
                  },
                  "client_company_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "share_new_items_with_clients": {
                    "type": "boolean",
                    "description": "The Clients tab's \"New things start as\": true starts every composer on shared and shares a create that omits `visible_to_clients`; false (the default) keeps everything team-only until shared. Needs `clients_enabled`, else 403; turning clients off resets it"
                  },
                  "timesheet_enabled": {
                    "type": "boolean",
                    "description": "The project's Timesheet switch; turning it off hides its timesheets and deletes nothing"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "description": "Unknown color, or a starts_on/ends_on pair rule violated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/audience": {
      "get": {
        "operationId": "getProjectAudience",
        "tags": [
          "Projects"
        ],
        "summary": "Who an item in the project reaches",
        "description": "The people and groups an item in this project reaches: everyone on the project's roster, every teammate when the project is open to everyone in the account, and clients only when the item is visible to clients. Removed people never appear, and a client caller sees only the people clients can see. This is the list the @-mention, assignee, subscriber and invitee pickers offer. `PUT /recordings/{id}/assignees` refuses (403) and `PUT /recordings/{id}/subscribers` skips anyone outside it, and a mention of anyone outside it is never delivered, so build pickers on this rather than on `GET /projects/{projectId}/people`. Pass the item's `visible_to_clients`, or for a new item the visibility it will have.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "visible_to_clients",
            "in": "query",
            "required": false,
            "description": "`true` for an item visible to clients (its audience includes the project's clients). Omitted or `false` means a team-only item.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The item's audience",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectAudience"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/events": {
      "get": {
        "operationId": "listProjectEvents",
        "tags": [
          "Projects"
        ],
        "summary": "The project timeline",
        "description": "The project's activity timeline. Rows carry a plain-text `excerpt`. Re-parenting moves and file downloads never appear here; they are change-log material on `GET /recordings/{id}/events`. Clients see only activity about client-visible recordings.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "1-based page number"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "description": "Page size, default 50, capped at 200"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Plain alternative to per_page"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only events at or after this instant"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list of event actions, e.g. card_added,move (a container-watch poll)"
          },
          {
            "name": "recording_type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only events about recordings of this type"
          },
          {
            "name": "parent_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only events about recordings inside this container (a column, a list): the container-watch poll"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Keyword filter"
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline events, newest first; while more exist, `Link: rel=\"next\"` names the next page",
            "headers": {
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectEvent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/health": {
      "get": {
        "operationId": "getProjectHealth",
        "tags": [
          "Progress & health"
        ],
        "summary": "The project health gauge",
        "description": "`enabled`, the current `status`/`position` (0 to 100), `updated_at`, and the update history newest first. Clients see only client-visible updates and derive their current state from those.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The gauge and its history",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthGauge"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setProjectHealthEnabled",
        "tags": [
          "Progress & health"
        ],
        "summary": "Turn the gauge on or off",
        "description": "Admins only: shows or hides the project's health gauge.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Toggled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/health/updates": {
      "post": {
        "operationId": "createHealthUpdate",
        "tags": [
          "Progress & health"
        ],
        "summary": "Post a health update",
        "description": "Move the needle: `position` (0 to 100) is required, `status` defaults to on_track. Creates a commentable, cheerable `health_update` recording. `notify` is the update's exact subscriber set; omitted, it falls back to the project notify audience (Thicket convention, vs bc3's omit meaning nobody). `visible_to_clients` defaults to false, hidden from clients. Only the note is editable afterwards (`PATCH /recordings/{id}`, author only); trashing the update rolls the needle back to the previous one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "position"
                ],
                "properties": {
                  "position": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "on_track",
                      "some_risk",
                      "concerned"
                    ],
                    "default": "on_track"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 50000,
                    "description": "Rich HTML; sanitized"
                  },
                  "notify": {
                    "description": "The exact subscriber set: everyone, none, or specific membership ids",
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "everyone",
                          "none"
                        ]
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    ]
                  },
                  "visible_to_clients": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created health_update recording's id",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/join": {
      "post": {
        "operationId": "joinProject",
        "tags": [
          "Projects"
        ],
        "summary": "Add yourself to a project",
        "description": "Owner-only: ordinary membership plus an activity event, on any project. Admins and members receive 403. This and `?scope=all` listing are the only cracks in the invite-only wall: owners may see and self-add, never silently read.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The joined project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/links": {
      "get": {
        "operationId": "listProjectLinks",
        "tags": [
          "Projects"
        ],
        "summary": "List project links",
        "description": "External links shown with the project's tools, in position order. Clients see only `visible_to_clients` rows.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Links in position order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectLink"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createProjectLink",
        "tags": [
          "Projects"
        ],
        "summary": "Add a project link",
        "description": "Clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "title"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 26
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "service": {
                    "type": "string",
                    "description": "The chooser tile (`other`, the External Service form, by default); an id the server does not know is a 422"
                  },
                  "visible_to_clients": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/links/{linkId}": {
      "patch": {
        "operationId": "updateProjectLink",
        "tags": [
          "Projects"
        ],
        "summary": "Update a project link",
        "description": "Any field, including `position` (finite number, fractional ok: the drag-reorder contract). Provide at least one field or receive 422. Clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "linkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 26
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "service": {
                    "type": "string",
                    "description": "The chooser tile (`other`, the External Service form, by default); an id the server does not know is a 422"
                  },
                  "visible_to_clients": {
                    "type": "boolean"
                  },
                  "position": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteProjectLink",
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project link",
        "description": "Hard delete: links are tool-level furniture with no trash lifecycle. Clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "linkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/links/{linkId}/image": {
      "put": {
        "operationId": "setProjectLinkImage",
        "tags": [
          "Projects"
        ],
        "summary": "Upload or replace a project link's image",
        "description": "Multipart `image` field, 10 MB or smaller, any decodable image (re-encoded to a square 160px webp). Clients receive 403, like every other link write.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "linkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "image"
                ],
                "properties": {
                  "image": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Image stored"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "removeProjectLinkImage",
        "tags": [
          "Projects"
        ],
        "summary": "Remove a project link's image",
        "description": "Back to the service tile. Clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "linkId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Image removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/logo": {
      "put": {
        "operationId": "uploadProjectLogo",
        "tags": [
          "Projects"
        ],
        "summary": "Upload or replace the project logo",
        "description": "Multipart `logo` field: any decodable image, 10 MB or smaller, re-encoded to a square webp with metadata stripped. Bad, missing, or oversize files are a 422. Project payloads then carry the same-origin serve path in `logo` (immutable-cacheable, no session needed: the per-upload UUID is the capability). Gated like every project-settings write: clients receive 403, and the Restrict-who-can-edit-project-details governance toggle honors the creator rule.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "logo"
                ],
                "properties": {
                  "logo": {
                    "type": "string",
                    "format": "binary",
                    "description": "The image file, 10 MB or smaller"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Stored"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteProjectLogo",
        "tags": [
          "Projects"
        ],
        "summary": "Remove the project logo",
        "description": "Same gates as the upload.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/notification-pref": {
      "get": {
        "operationId": "getProjectNotificationPref",
        "tags": [
          "Projects"
        ],
        "summary": "Get the project bell",
        "description": "Your per-project notification preference. Resolution order: thread subscription, then project, then account default.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The preference; null means the account default applies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "scope"
                  ],
                  "properties": {
                    "scope": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "everything",
                        "mentions",
                        null
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setProjectNotificationPref",
        "tags": [
          "Projects"
        ],
        "summary": "Set the project bell",
        "description": "Notify about everything vs only when mentioned; null reverts to the account default.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scope"
                ],
                "properties": {
                  "scope": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "everything",
                      "mentions",
                      null
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored preference",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "scope"
                  ],
                  "properties": {
                    "scope": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "everything",
                        "mentions",
                        null
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/people": {
      "get": {
        "operationId": "listProjectPeople",
        "tags": [
          "Projects"
        ],
        "summary": "People on the project",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The project's people",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectPerson"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setProjectPeople",
        "tags": [
          "Projects"
        ],
        "summary": "Change who has access",
        "description": "Either replace the explicit access list with `membership_ids`, or apply deltas: `grant` and `revoke` take membership ids, and `create` invites someone new straight into this project (the grant rides the invitation). Provide at least one of the four. Clients can only be granted while the project works with clients; otherwise 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Replaces the project's explicit access list"
                  },
                  "grant": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "revoke": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "create": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "email_address"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "email_address": {
                          "type": "string",
                          "format": "email"
                        },
                        "title": {
                          "type": "string",
                          "maxLength": 200
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resulting roster plus the emails invited",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "granted",
                    "invited"
                  ],
                  "properties": {
                    "granted": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "membership_id",
                          "name"
                        ],
                        "properties": {
                          "membership_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "role": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "invited": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "email"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/share-everything": {
      "get": {
        "operationId": "previewShareEverything",
        "tags": [
          "Projects"
        ],
        "summary": "What Share everything would share",
        "description": "Counts what `POST` would share with the project's client right now, by kind: anchors (messages, to-do lists, docs, files, links and folders at the top of Docs & Files, events, check-in questions, updates), the whole-tool `tools` still team-only, and `within`, everything that comes along inside them (to-dos, cards, comments…). Timesheets, loose to-dos, drafts and the trash never count. The team's.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What would be shared",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShareEverythingCounts"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "shareEverything",
        "tags": [
          "Projects"
        ],
        "summary": "Share everything with the client",
        "description": "Shares everything in the project with its client in one step (the Clients tab's \"Share everything…\", after its confirmation) and answers with what it shared. Timesheets, loose to-dos, drafts and the trash stay the team's. The project's clients follow every newly shared thread, and each anchor's change log records the share. 403 for a client caller or a project that doesn't work with clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What was shared",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShareEverythingCounts"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/star": {
      "put": {
        "operationId": "starProject",
        "tags": [
          "Projects"
        ],
        "summary": "Star a project",
        "description": "Personal: starred projects pin to the top of home.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Starred",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "starred"
                  ],
                  "properties": {
                    "starred": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "unstarProject",
        "tags": [
          "Projects"
        ],
        "summary": "Unstar a project",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unstarred",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "starred"
                  ],
                  "properties": {
                    "starred": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/status/{status}": {
      "put": {
        "operationId": "setProjectStatus",
        "tags": [
          "Projects"
        ],
        "summary": "Change the project lifecycle",
        "description": "The one lifecycle sub-path: activate, archive, or trash the project. Restoring from the trash is the same call with active.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The project in its new status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/timesheet": {
      "get": {
        "operationId": "listProjectTimesheet",
        "tags": [
          "Timesheets"
        ],
        "summary": "A project's timesheet",
        "description": "Every counted entry in the project, on the project itself and on its items, newest day first then newest logged. Absence is never here. 404 while the project's Timesheet is off, and for clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "1-based page number"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Page size, default and cap 100"
          }
        ],
        "responses": {
          "200": {
            "description": "Entries, newest day first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimesheetEntry"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/timesheet/csv": {
      "get": {
        "operationId": "downloadProjectTimesheetCsv",
        "tags": [
          "Timesheets"
        ],
        "summary": "A project's timesheet as CSV",
        "description": "The project's timesheet as a CSV file: every counted entry, on the project itself and on its items, unpaginated. 404 while the project's Timesheet is off, and for clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV file (Content-Disposition: attachment; filename=\"timesheet-YYYY-MM-DD.csv\", dated the caller's today)",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "UTF-8 with no BOM, LF line endings and RFC 4180 quoting. Header `Date,Person,Hours,Regular,Overtime,Project,Item,Notes,Created` (then `Status` while approvals are on), then one row per counted entry, newest day first, then newest logged. No totals row. Hours is the combined total; Regular and Overtime are its two portions, all as decimal strings (\"2.0\", \"1.5\", \"0.12\"); Item is \"{Type}: {title}\" (To-do, Message, Document, File, Card or Event), empty for time on the project itself; Created is ISO 8601 with the caller's UTC offset. A Person, Project, Item or Notes value that starts with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading apostrophe, so spreadsheets read it as text, never as a formula. While approvals are on, a last `Status` column says where each entry's week stands: Approved, Submitted, Changed, Rejected or Not submitted."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/timesheet/entries": {
      "post": {
        "operationId": "createProjectTimesheetEntry",
        "tags": [
          "Timesheets"
        ],
        "summary": "Log time on a project",
        "description": "Time on the project itself, not on any one item: the entry's `parent` is the project's timesheet (`type: \"timesheet\"`), made with the first. The project must have its Timesheet on and be neither archived nor in the trash. Writes no activity and sends no notification.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "date",
                  "hours"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "The day the time was spent; future days are accepted"
                  },
                  "hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Combined regular and overtime hours, like 1.5 or 1:30. Must be positive; at most 24 hours a person a day across entries."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000,
                    "description": "Notes"
                  },
                  "person_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose time (a membership id); defaults to the caller. Admins and owners may name anyone on the project's team; members only themselves (403 otherwise)"
                  },
                  "overtime_hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Overtime portion of the combined hours, like 1.5 or 1:30. Must be between zero and hours. Defaults to zero on create; omitted on update, preserves existing overtime up to the new total."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetEntry"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation (code `invalid`: hours not like 1.5 or 1:30, zero hours, a bad day, a repeating event without `occurrence`, a person who isn't a non-client member of the project; archived and trashed items take no new time) or the 24-hour daily cap (code `daily_cap`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/timesheet/items": {
      "get": {
        "operationId": "listProjectTimesheetItems",
        "tags": [
          "Timesheets"
        ],
        "summary": "List what can take time in a week",
        "description": "For adding a row to the weekly timesheet, in the order to show them and filed the way each tool files them (`container`): the project's to-dos list by list (a list's own to-dos, then each group in it; a subtask right after its to-do), every event in the week (one per day of a repeating one), messages (the most recently changed first), documents and files folder by folder (a folder's own, then each subfolder), and cards column by column (Triage, the columns, Not now, Done). Up to 250 of each type. Only what a tool that's on holds, through lists and folders that are active. A to-do or card completed before the week began is left out unless `q` matches its title. `404` while the project's Timesheet is off.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "week",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Any day in the week; default today in the caller's zone. The week runs from the org's week start"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Narrows to titles that match, and to what sits in a list, to-do group, folder or card table column whose name matches"
          }
        ],
        "responses": {
          "200": {
            "description": "The items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimesheetPickerItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/tools": {
      "get": {
        "operationId": "listProjectTools",
        "tags": [
          "Projects"
        ],
        "summary": "The project's tools",
        "description": "Each enabled tool has exactly one singleton container recording (its `container_id`); content is created under it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool list in position order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolEntry"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/tools/{tool}": {
      "put": {
        "operationId": "updateProjectTool",
        "tags": [
          "Projects"
        ],
        "summary": "Configure one tool",
        "description": "Toggle the tool, rename it on this project (`title: null` restores the default name; a per-project rename wins over the org-wide override), and/or reposition it in the tool list (finite number, fractional ok: the drag-reorder contract). Provide at least one field or receive 422. Clients receive 403. Returns the whole tool list. Legacy tool names (todoset, vault, schedule, card_table, questionnaire, clientside) are still accepted on the path segment during the vocabulary transition.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tool",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "message_board",
                "todos",
                "folder",
                "calendar",
                "board",
                "chat",
                "check_ins",
                "clients"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "description": "null restores the default name"
                  },
                  "position": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole tool list after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolEntry"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/projects/{projectId}/trash": {
      "get": {
        "operationId": "listProjectTrash",
        "tags": [
          "Projects"
        ],
        "summary": "The project trash",
        "description": "The project trash page. Members see the items they created or trashed, org admins see the project's whole trash, clients only client-visible items. Restore rides the generic `PUT /recordings/{id}/status/active`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Trashed items visible to you",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectTrashItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "emptyProjectTrash",
        "tags": [
          "Projects"
        ],
        "summary": "Empty the project trash",
        "description": "Permanently destroys the same set the GET shows you: your own items for members, the whole project trash for admins. Irreversible.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "How many recordings were destroyed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/public-items": {
      "get": {
        "operationId": "listPublicItems",
        "tags": [
          "Recordings"
        ],
        "summary": "List every active public link",
        "description": "Owner-only: the Admin inventory of everything shared at /public/{token}. Non-owners get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The active links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "recording_id",
                      "url",
                      "type",
                      "created_at"
                    ],
                    "properties": {
                      "recording_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "The public capability URL"
                      },
                      "type": {
                        "$ref": "#/components/schemas/RecordingType"
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "project_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid"
                      },
                      "project_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "created_by_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "revokeAllPublicItems",
        "tags": [
          "Recordings"
        ],
        "summary": "Unpublish everything",
        "description": "Owner-only: revoke every active public link at once. Non-owners get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "How many links were revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "revoked"
                  ],
                  "properties": {
                    "revoked": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/public-items/{recordingId}": {
      "delete": {
        "operationId": "revokePublicItem",
        "tags": [
          "Recordings"
        ],
        "summary": "Unpublish one item",
        "description": "Owner-only: revoke one recording's public link from the Admin list. Non-owners get 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reassign": {
      "post": {
        "operationId": "reassignTodos",
        "tags": [
          "Trash & admin"
        ],
        "summary": "Bulk-change a person's open to-do assignments",
        "description": "Owner only, account-wide or in one project. Three modes: `reassign` moves them to someone else (the default), `remove` takes them off everything (no target), `add` adds someone alongside while the source keeps theirs. Completed history is untouched; on reassign and add the target inherits the source's subscriptions on the affected items; one summary event is logged per affected project; the acting owner gets an email confirmation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "from_id"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "reassign",
                      "remove",
                      "add"
                    ],
                    "default": "reassign"
                  },
                  "from_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "to_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Required unless mode is remove"
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Omit for account-wide"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What changed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ReassignImpact"
                    },
                    {
                      "type": "object",
                      "required": [
                        "email_queued"
                      ],
                      "properties": {
                        "email_queued": {
                          "type": "boolean",
                          "description": "Whether the confirmation email was queued"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reassign/preview": {
      "get": {
        "operationId": "previewReassign",
        "tags": [
          "Trash & admin"
        ],
        "summary": "Preview a bulk assignment change",
        "description": "Owner only: how many open to-dos the change would touch, per project. Open to-dos only; `to` is unused for mode=remove.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The source membership id"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The target membership id; required unless mode=remove"
          },
          {
            "name": "project",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Limit to one project"
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "reassign",
                "remove",
                "add"
              ],
              "default": "reassign"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The would-be impact",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReassignImpact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings": {
      "get": {
        "operationId": "listRecordings",
        "tags": [
          "Recordings"
        ],
        "summary": "Query recordings across projects",
        "description": "The generic cross-project query: every row of the given `type` you can access, any project. Filter with `project_id` and `status`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "message_board",
                "message",
                "comment",
                "todos",
                "todolist",
                "todo",
                "folder",
                "document",
                "upload",
                "calendar",
                "calendar_event",
                "board",
                "column",
                "card",
                "step",
                "chat",
                "chat_message"
              ]
            },
            "description": "The recording type to list"
          },
          {
            "name": "project_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching recordings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Recording"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}": {
      "get": {
        "operationId": "getRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Fetch one recording",
        "description": "Any recording, flat route. Includes the author byline (`creator_name`, `creator_image`; names survive the person leaving the org) and `content_attachments`, structured metadata for every file embedded in the rich text (always present, [] when none). Drafts return to any non-client project member by link but every write stays author-only; clients always 404 on drafts.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The recording with byline and attachment metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordingDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Edit a recording",
        "description": "All fields stay editable after creation. To-dos use `content`/`content_html` for notes and `starts_on`..`due_on` for a date range. `recurrence` is to-dos only (422 otherwise): validated and merged into `meta.recurrence` without clobbering sibling keys, `null` stops the series; editing the primary re-projects every FUTURE occurrence while already-spawned instances keep their own fields. `scheduled_at` re-schedules a draft (`null` removes the schedule). `base_name` renames an upload, keeping the extension. `schedule` and `membership_ids` are check-in questions only (new askees get subscribed). Comment and chat-line edits are author-only and stamp `meta.editedAt`; the governance 15-minute edit window returns 403 once closed. The Not Now and Done card-table columns keep their `title` and their description (`content`/`content_html`): editing either refuses (404).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000
                  },
                  "content": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50000,
                    "description": "Plain text; converted to safe HTML server-side"
                  },
                  "content_html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100000,
                    "description": "Rich HTML; sanitized server-side. Wins over content if both sent"
                  },
                  "due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  },
                  "starts_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "To-do date range start (date, not timestamp; pairs with due_on)"
                  },
                  "starts_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "all_day": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "category_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "scheduled_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Drafts only: publish automatically at this time; null removes the schedule"
                  },
                  "meta": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": true
                  },
                  "base_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280,
                    "description": "Uploads only: rename the file, the extension is kept"
                  },
                  "schedule": {
                    "$ref": "#/components/schemas/QuestionSchedule"
                  },
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "description": "Questions only: replace who's asked (new askees get subscribed)"
                  },
                  "recurrence": {
                    "$ref": "#/components/schemas/RecordingRecurrence",
                    "description": "To-dos only: set/replace the repeat schedule (merged into meta.recurrence); null stops the series"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteRecordingLine",
        "tags": [
          "Recordings"
        ],
        "summary": "Hard-delete a chat or DM line",
        "description": "Immediate, permanent deletion, chat and DM lines only (author or admin+); these never enter the trash. Everything else, comments included, goes through PUT .../status/trashed instead. Under the governance 15-minute window, authors lose delete once it closes (403); admins keep it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/answers": {
      "get": {
        "operationId": "listAnswers",
        "tags": [
          "Check-ins"
        ],
        "summary": "List a question's answers",
        "description": "Answers to a check-in question, newest day first. Each answer is stamped with its day (`starts_on`); rows carry `creator_name` and `comment_count`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The check-in question's id"
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Only answers for this day"
          },
          {
            "name": "creator_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only answers by this membership"
          }
        ],
        "responses": {
          "200": {
            "description": "The answers, newest day first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Recording"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "creator_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "comment_count": {
                            "type": "integer"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "post": {
        "operationId": "createAnswer",
        "tags": [
          "Check-ins"
        ],
        "summary": "Answer a check-in question",
        "description": "Answers the given day, default today; any day is pickable. Send `content` (plain text) or `content_html` (rich, sanitized); one is required. Answers are commentable and cheerable, stamped with their day on `starts_on`, and notify the question's subscribers, never the poster. Editing afterwards is author-only (PATCH `content` or `starts_on` on the answer; anyone else gets 404).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The check-in question's id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "One of `content` or `content_html` is required",
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 50000,
                    "description": "Plain text, converted to HTML"
                  },
                  "content_html": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "Rich HTML, sanitized server-side"
                  },
                  "occurs_on": {
                    "type": "string",
                    "format": "date",
                    "description": "The day being answered; default today"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created answer recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/answers/by": {
      "get": {
        "operationId": "listAnswerers",
        "tags": [
          "Check-ins"
        ],
        "summary": "List who has answered",
        "description": "Everyone who has answered the question, newest answer first (bc3's list-answerers convention).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The check-in question's id"
          }
        ],
        "responses": {
          "200": {
            "description": "The answerers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "membership_id",
                      "name",
                      "answer_count",
                      "latest_on"
                    ],
                    "properties": {
                      "membership_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string"
                      },
                      "answer_count": {
                        "type": "integer"
                      },
                      "latest_on": {
                        "type": "string",
                        "format": "date",
                        "description": "The day of their most recent answer"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/approval": {
      "put": {
        "operationId": "decideApproval",
        "tags": [
          "Clients"
        ],
        "summary": "Decide an approval",
        "description": "The assigned approver decides, one shot: anyone else gets 403, and an already-decided approval also returns 403. The decision lands on the recording's `approval_status` and subscribers are notified.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The client_approval recording's id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decision"
                ],
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided approval recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/approvals": {
      "post": {
        "operationId": "createApproval",
        "tags": [
          "Clients"
        ],
        "summary": "Ask a client to approve something",
        "description": "Creates a `client_approval` under the project's clients container; `approver_id` must be a client membership on the project. Approvals are requested by the team, not clients (403). List them via GET /recordings/{clientsId}/children?type=client_approval, whose rows carry `assignees` (the first is the approver).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The project's clients container id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "approver_id"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "content_html": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "Rich HTML, sanitized server-side"
                  },
                  "due_on": {
                    "type": "string",
                    "format": "date"
                  },
                  "approver_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The client membership asked to approve"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created approval recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/assignees": {
      "get": {
        "operationId": "listAssignees",
        "tags": [
          "Recordings"
        ],
        "summary": "List assignees",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Who's assigned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingPersonRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setAssignees",
        "tags": [
          "Recordings"
        ],
        "summary": "Replace the assignee set",
        "description": "Assignment is always many-capable: PUT replaces the WHOLE set (always an array; [] clears). New assignees are notified and subscribed, and so is whoever assigned them: assigning follows the work. Everyone newly added must be able to open the recording (on the project's people, or a teammate on a project open to everyone; a client only on client-visible work; never a removed person), otherwise 403 and nothing changes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "membership_ids"
                ],
                "properties": {
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replaced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/cards": {
      "post": {
        "operationId": "createCardWithSteps",
        "tags": [
          "Recordings"
        ],
        "summary": "Create a card with assignments and ordered steps atomically",
        "description": "All content commits in one transaction. Invalid steps, assignments or parent access roll back the entire operation. Notifications are queued durably and delivered after commit. Returns a recording with created_step_count. Requires a column parent.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 10000,
                    "minLength": 1
                  },
                  "content_html": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "Rich HTML; sanitized server-side. Wins over content if both sent"
                  },
                  "due_on": {
                    "type": "string",
                    "format": "date"
                  },
                  "assignee_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  },
                  "steps": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 10000
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created recording",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Recording"
                    },
                    {
                      "type": "object",
                      "required": [
                        "created_step_count"
                      ],
                      "properties": {
                        "created_step_count": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 100
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/cheers": {
      "get": {
        "operationId": "listCheers",
        "tags": [
          "Recordings"
        ],
        "summary": "List a recording's cheers",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cheers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingCheerRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "addCheer",
        "tags": [
          "Recordings"
        ],
        "summary": "Leave a cheer",
        "description": "A short reaction: up to 16 characters of emoji or text. Rejected while comments are closed, on drafts, and on uncheerable types (404). Cheering notifies the recording's author only, on their own channel gated solely by their notify_cheers preference (never the notify scope), bundled into one notification per recording within 3 hours. Remove one via DELETE /cheers/{cheer_id}.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The cheer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "content"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "content": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "You already left that exact cheer (code duplicate)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/children": {
      "get": {
        "operationId": "listChildren",
        "tags": [
          "Recordings"
        ],
        "summary": "List a container's children",
        "description": "Default order is position/created; `sort`+`direction` choose another order. With `sort=created_at` the listing pages: `limit` sizes a page and the `Link: rel=\"next\"` header's URL carries the next page's opaque `cursor`, in the requested direction (chat history walks back with `direction=desc`). The older `before` instant still answers but can skip a row that shares its millisecond. Rows carry `creator_name`, `creator_image`, `comment_count`, `last_comment_at` so list surfaces render bylines and comment badges without N+1 lookups; `chat_message` rows additionally carry their `cheers`, `client_approval` and `card` rows their `assignees` (with `image`), `card` rows `step_count`/`steps_done`, `column` rows `card_count`, `todolist` rows `todo_open_count`/`todo_done_count` (groups included), and `upload` rows `byte_size`/`content_type`: everything a list row shows, batched, never a fetch per row. `status=drafted` only ever returns the caller's own drafts.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "message",
                "comment",
                "todolist",
                "todo",
                "document",
                "upload",
                "linked_file",
                "folder",
                "calendar_event",
                "column",
                "card",
                "step",
                "chat_message",
                "question",
                "answer",
                "client_approval",
                "client_correspondence"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed",
                "drafted"
              ]
            }
          },
          {
            "name": "completed",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only children created before this instant. Superseded by `cursor`: a row in the same millisecond as the instant can be skipped"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The children, enriched list rows; with `sort=created_at` and more to come, `Link: rel=\"next\"` names the next page",
            "headers": {
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ChildListRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "post": {
        "operationId": "createChild",
        "tags": [
          "Recordings"
        ],
        "summary": "Create content under a container",
        "description": "Creates any content type under its container; parent rules are validated server-side (message under message_board, todolist under todos or todolist as a group, todo under todolist/todos/todo as a subtask, document/upload/linked_file/folder under folder, calendar_event under calendar, column under board, card under column, step under card, chat_message under chat or dm, question under check_ins, answer under question). `url` is required for linked_file (stored on meta.url). `notify` applies to messages, documents, and linked files: \"everyone\" (default), \"none\", or membership ids; the resolved set is notified when the item posts and becomes its exact subscriber set (\"none\" subscribes nobody, not even the author), honored when a draft or scheduled post publishes; picked people outside the project audience are dropped. `completion_subscriber_ids` are subscribed at creation and notified on completion. `recurrence` needs a dated to-do (422 otherwise) and makes it a recurring series' primary. `status: \"drafted\"` keeps it private to you until published; add `scheduled_at` to publish automatically. Clients can't add loose to-dos (403).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "message",
                      "todolist",
                      "todo",
                      "document",
                      "folder",
                      "linked_file",
                      "calendar_event",
                      "column",
                      "card",
                      "step",
                      "chat_message",
                      "question",
                      "answer",
                      "client_correspondence"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 50000,
                    "description": "Plain text; converted to safe HTML server-side"
                  },
                  "content_html": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "Rich HTML; sanitized server-side. Wins over content if both sent"
                  },
                  "due_on": {
                    "type": "string",
                    "format": "date"
                  },
                  "starts_on": {
                    "type": "string",
                    "format": "date",
                    "description": "To-do date range start (pairs with due_on)"
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "all_day": {
                    "type": "boolean"
                  },
                  "position": {
                    "type": "number",
                    "description": "Explicit sort position (fractional ok); omitted appends to the end"
                  },
                  "assignee_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "completion_subscriber_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "When done, notify: subscribed at creation, notified on completion"
                  },
                  "visible_to_clients": {
                    "type": "boolean"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "drafted"
                    ],
                    "description": "drafted keeps it private to you until published"
                  },
                  "scheduled_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "With status drafted: publish automatically at this time"
                  },
                  "category_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "meta": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Type-specific settings"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000,
                    "description": "Linked files only: where the file lives; required for the type"
                  },
                  "notify": {
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "everyone",
                          "none"
                        ]
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    ],
                    "description": "Messages, documents, linked files: who's notified when this posts; they become the thread's exact subscriber set"
                  },
                  "recurrence": {
                    "$ref": "#/components/schemas/RecordingRecurrence",
                    "description": "Dated to-dos only: the repeat schedule, stored as meta.recurrence"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/color": {
      "put": {
        "operationId": "setRecordingColor",
        "tags": [
          "Recordings"
        ],
        "summary": "Set the color",
        "description": "Color band on a to-do group or card column, or icon tint on a Docs & Files item (folder, doc, file, linked file); `white` or null clears. The special Triage/Not Now/Done columns and the root folder refuse (404).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "color"
                ],
                "properties": {
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "white",
                      "red",
                      "orange",
                      "yellow",
                      "green",
                      "blue",
                      "aqua",
                      "purple",
                      "gray",
                      "pink",
                      "brown",
                      null
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recolored recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/comments": {
      "get": {
        "operationId": "listComments",
        "tags": [
          "Recordings"
        ],
        "summary": "List a thread's comments",
        "description": "Comments work on any commentable recording, one endpoint for all types (card steps take none). Rows carry `creator_name`/`creator_image` so threads render bylines directly.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The comments with author bylines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CommentRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createComment",
        "tags": [
          "Recordings"
        ],
        "summary": "Comment on a recording",
        "description": "Needs `content` or `content_html`. Closed threads, archived/trashed parents, and uncommentable types (card steps) refuse with 404. Edits afterwards are author-only via PATCH /recordings/{comment_id} (stamps meta.editedAt); comments trash via PUT .../status/trashed, never DELETE.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 50000,
                    "description": "Plain text; converted to safe HTML server-side"
                  },
                  "content_html": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "Rich HTML; sanitized server-side. Wins over content if both sent"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/comments-closed": {
      "put": {
        "operationId": "setCommentsClosed",
        "tags": [
          "Recordings"
        ],
        "summary": "Close or reopen comments",
        "description": "Author or admin+ only (others 404). Closed threads reject comments AND cheers.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "closed"
                ],
                "properties": {
                  "closed": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/completion": {
      "put": {
        "operationId": "completeRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Mark done",
        "description": "Completion (todos, cards, steps) is orthogonal to status; the payload carries provenance (`completed_at`, `completed_by_id`). Completing a to-do notifies its subscribers, excluding whoever checked it off; completing a subtask never touches the parent and pings the parent to-do's subscribers plus the subtask's own. Cards: completion IS the Done column, so PUT moves the card to its board's Done column (top slot) and stamps it. Completing a recurring primary immediately spawns the next occurrence.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The completed recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "reopenRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Reopen",
        "description": "Clears the completion stamp. Cards move back to the column they were completed from (else Triage).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The reopened recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/copy": {
      "post": {
        "operationId": "copyRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Deep-copy under a new parent",
        "description": "Deep copy under the new parent, any project. Comments stay behind unless `include_comments: true` (included comments keep their original author and timestamps). Parent rules and subtask depth apply to the target; copying a draft yields another (unscheduled) draft.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "parent_id"
                ],
                "properties": {
                  "parent_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "include_comments": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The copy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/events": {
      "get": {
        "operationId": "listRecordingEvents",
        "tags": [
          "Recordings"
        ],
        "summary": "Read the change log",
        "description": "The per-recording \"History of changes\": every lifecycle event, oldest first (created, completed, moved with fromParentTitle/toParentTitle captured at move time, version_added, visibility/hold/comment toggles). Commented rows stay out: the thread is that history. Each row includes its cheers.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Only changes after this instant"
          }
        ],
        "responses": {
          "200": {
            "description": "The change log, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingEvent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/hold": {
      "put": {
        "operationId": "setRecordingHold",
        "tags": [
          "Recordings"
        ],
        "summary": "Toggle On Hold",
        "description": "On Hold is per column. On a card: parks it in its column's ON HOLD section WITHOUT changing its column (the column's section must be enabled; cards in Triage/Not Now/Done refuse; moving the card to another column releases it). On a column: enables/disables the section itself (disabling releases every held card; Triage/Not Now/Done refuse).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "on_hold"
                ],
                "properties": {
                  "on_hold": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated card or column",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/opt-out": {
      "put": {
        "operationId": "optOutQuestion",
        "tags": [
          "Recordings"
        ],
        "summary": "Opt out of a check-in question",
        "description": "Per-person \"stop asking me\" on a check-in question; affects only the caller.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "opted_out"
                ],
                "properties": {
                  "opted_out": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new opt-out state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "opted_out"
                  ],
                  "properties": {
                    "opted_out": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/pause": {
      "put": {
        "operationId": "pauseQuestion",
        "tags": [
          "Recordings"
        ],
        "summary": "Pause or resume a check-in question",
        "description": "Stop/resume asking a check-in question; the ask sweep skips paused questions. The recording must be a question (404 otherwise).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "paused"
                ],
                "properties": {
                  "paused": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new pause state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "paused"
                  ],
                  "properties": {
                    "paused": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/pin": {
      "put": {
        "operationId": "pinRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Pin a message",
        "description": "Pins a message to the top of its board; messages only, and at most 10 pinned per board (the 11th refuses).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The pinned message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "unpinRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Unpin a message",
        "description": "Clears the pin.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The unpinned message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/position": {
      "put": {
        "operationId": "moveRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Move and/or reorder",
        "description": "Kanban moves, drag reorders, and moving a to-do into/out of/between lists and groups (a group is a todolist, so `parent_id` is the destination list or group; the todos container = loose). `position` is fractional: drops send the midpoint between neighbors. Cross-project parents move the whole subtree; a parent inside the recording's own subtree (folder/group cycles) returns 404. Position-only calls (pure reorders) don't log a moved activity event; re-parents do. Moving a card across the Done boundary completes it (in) or clears the stamp (out).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parent_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The destination container; omit to reorder in place"
                  },
                  "position": {
                    "type": "number",
                    "description": "Fractional sort position (drag surfaces send the midpoint between neighbors)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/progress": {
      "get": {
        "operationId": "getProgressChart",
        "tags": [
          "Progress & health"
        ],
        "summary": "Get the progress chart",
        "description": "The todos container's progress chart. Charts track to-do lists, not items: each tracked list is a dot positioned 0 to 1 along the arc (left = working it out, right = getting it done) with a stable `color`. Returns the `enabled` flag, the dots (`lists`), saved `updates` newest first (each with its snapshot `positions`), and the legacy move `history`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The project's todos container id"
          }
        ],
        "responses": {
          "200": {
            "description": "The chart",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "enabled",
                    "updated_at",
                    "lists",
                    "updates",
                    "history"
                  ],
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "description": "True while at least one list is tracked"
                    },
                    "updated_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time",
                      "description": "When the newest saved update landed"
                    },
                    "lists": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProgressDot"
                      }
                    },
                    "updates": {
                      "type": "array",
                      "description": "Saved progress updates, newest first",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The progress_update recording's id"
                          },
                          "note": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Sanitized HTML"
                          },
                          "creator_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "visible_to_clients": {
                            "type": "boolean"
                          },
                          "positions": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/ProgressSnapshotDot"
                            },
                            "description": "The whole chart as it stood when the update was saved"
                          }
                        }
                      }
                    },
                    "history": {
                      "type": "array",
                      "description": "Legacy per-move log, newest first",
                      "items": {
                        "type": "object",
                        "properties": {
                          "detail": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "additionalProperties": true
                          },
                          "actor_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateProgress",
        "tags": [
          "Progress & health"
        ],
        "summary": "Move a dot or change tracking settings",
        "description": "Two shapes depending on the target. On a todolist: `{position, note?}` moves that list's dot (0 to 1; null stops tracking; the color assigned at first track is kept across re-tracks) and returns `{ok: true}`. On the todos container: bulk settings `{tracked: [ids], untracked: [ids]}` start or stop tracking whole lists and return the refreshed chart.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A todolist id (move one dot) or the todos container id (bulk settings)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "title": "Move one list's dot (todolist target)",
                    "required": [
                      "position"
                    ],
                    "properties": {
                      "position": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 1,
                        "description": "The dot's new position; null stops tracking"
                      },
                      "note": {
                        "type": "string",
                        "maxLength": 2000
                      }
                    }
                  },
                  {
                    "type": "object",
                    "title": "Bulk tracking settings (todos target)",
                    "properties": {
                      "tracked": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "description": "Todolist ids to start tracking (at position 0)"
                      },
                      "untracked": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "description": "Todolist ids to stop tracking"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "For a todolist target `{ok: true}`; for the todos target the refreshed chart",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "title": "Dot moved",
                      "required": [
                        "ok"
                      ],
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "title": "Refreshed chart",
                      "required": [
                        "enabled",
                        "lists"
                      ],
                      "properties": {
                        "enabled": {
                          "type": "boolean"
                        },
                        "lists": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ProgressDot"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/progress/updates": {
      "post": {
        "operationId": "createProgressUpdate",
        "tags": [
          "Progress & health"
        ],
        "summary": "Save a progress update",
        "description": "Save this update: apply the moved dots, then snapshot the whole chart into a `progress_update` recording (commentable and cheerable). `notify` is the exact subscriber set, M5 semantics: everyone, none, or a membership id list. Trashing an update (PUT /recordings/{id}/status/trashed) rolls its moves back; dot positions derive from the chain of active updates.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The project's todos container id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "positions": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 1
                    },
                    "description": "Map of todolist id to its new dot position; default empty (snapshot only)"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 50000,
                    "description": "Rich HTML, sanitized server-side"
                  },
                  "notify": {
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "everyone",
                          "none"
                        ]
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    ],
                    "description": "Who is notified and becomes the update's exact subscriber set"
                  },
                  "visible_to_clients": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The saved update",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "created_at"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "positions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProgressSnapshotDot"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/public-link": {
      "get": {
        "operationId": "getPublicLink",
        "tags": [
          "Recordings"
        ],
        "summary": "Get a recording's public link",
        "description": "Whether the recording is shared read-only at /public/{token}, and where.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The link state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicLinkState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "publishPublicLink",
        "tags": [
          "Recordings"
        ],
        "summary": "Publish a public link",
        "description": "Shares one message, document, or file read-only at /public/{token}: a 256-bit token, revocable, noindex, sanitized content only, no comments, no subtree. Idempotent while a link is active. Clients can't publish, and when the org restricts public links only owners and admins can (403). An item in an archived or trashed project can't be published (403), and an existing link shows nothing until the project is active again.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Published; `url` is the capability URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicLinkState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "revokePublicLink",
        "tags": [
          "Recordings"
        ],
        "summary": "Unpublish a public link",
        "description": "Revokes the link; the public URL dies immediately.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unpublished",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicLinkState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/publish": {
      "post": {
        "operationId": "publishRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Publish a draft now",
        "description": "Publishes one of YOUR drafts immediately; anyone but the draft's creator gets 404. Publishing fires the create-time notifications, honoring the draft's `notify` choice.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/questions": {
      "get": {
        "operationId": "listQuestions",
        "tags": [
          "Check-ins"
        ],
        "summary": "List a project's check-in questions",
        "description": "Questions under the project's check_ins container. Rows carry `schedule`, `askees`, `answer_count`, and `recent_answer_count` (last 24 hours).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The project's check_ins container id"
          }
        ],
        "responses": {
          "200": {
            "description": "The questions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/Recording"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "schedule": {
                            "$ref": "#/components/schemas/CheckInSchedule"
                          },
                          "askees": {
                            "type": "array",
                            "items": {
                              "$ref": "#/components/schemas/CheckInAskee"
                            }
                          },
                          "answer_count": {
                            "type": "integer"
                          },
                          "recent_answer_count": {
                            "type": "integer",
                            "description": "Answers in the last 24 hours"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createQuestion",
        "tags": [
          "Check-ins"
        ],
        "summary": "Create a check-in question",
        "description": "Askees (`membership_ids`) are auto-subscribed to answers and notified per occurrence. Schedule semantics: daily takes `days` (0=Sunday to 6=Saturday, default Monday to Friday, weekends allowed); weekly and every_other_week take `day_of_week` (the alternation anchors at creation); monthly means the first `day_of_week` of the month, or a fixed `day_of_month`. Clients can't create check-ins (403). Every askee must be on the project (a teammate on a project open to everyone counts), and a client only when the question is visible to clients; anyone else is 403, on create and when `membership_ids` adds people. Question edits go through the generic PATCH /recordings/{id}: `title`, plus question-only `schedule` and `membership_ids`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The project's check_ins container id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "schedule",
                  "membership_ids"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  },
                  "schedule": {
                    "$ref": "#/components/schemas/CheckInSchedule"
                  },
                  "membership_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "description": "Who gets asked"
                  },
                  "visible_to_clients": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created question recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/references": {
      "get": {
        "operationId": "listRecordingReferences",
        "tags": [
          "Recordings"
        ],
        "summary": "List what links here",
        "description": "Recordings whose content links to this one, enriched for the References tab (project_name, creator_name, referenced_at, excerpt) and filtered to what the caller can access: project access, clients see client-visible only, DM sources show to participants only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The referencing recordings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingReference"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/status/{status}": {
      "put": {
        "operationId": "setRecordingStatus",
        "tags": [
          "Recordings"
        ],
        "summary": "Archive, trash, or restore",
        "description": "Works on ANY type; trash is recoverable. Tool containers refuse (except a folder with a parent, which archives and trashes with its contents cascading); drafts must publish before archiving, and `active` on a draft is 404 (publish it with POST /recordings/{recordingId}/publish); restoring a trashed draft returns it to drafted, never published. Clients may trash and restore only what they posted and never archive or unarchive (403). The governance restrict-content-actions toggle can refuse non-authors (403).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The recording in its new state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/subscribers": {
      "get": {
        "operationId": "listSubscribers",
        "tags": [
          "Recordings"
        ],
        "summary": "List who gets notified",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subscriber set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingPersonRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "updateSubscribers",
        "tags": [
          "Recordings"
        ],
        "summary": "Add or remove subscribers",
        "description": "Deltas, not replacement: `add` and `remove` are membership id lists. `notify: \"now\"` pings added people immediately; `\"next\"` stays quiet until the next comment. People in `add` who can't open the recording (off an invite-only project, a client on team-only work, removed) are skipped. Returns the updated subscriber set.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "add": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "remove": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "notify": {
                    "type": "string",
                    "enum": [
                      "now",
                      "next"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subscriber set",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RecordingPersonRow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/subscription": {
      "get": {
        "operationId": "getSubscription",
        "tags": [
          "Recordings"
        ],
        "summary": "Check your subscription",
        "description": "Your subscription on the recording plus the full subscriber list. On a card column this is \"watch this column\" (subscribers hear about new cards and column comments); on a chat room it is the chat bell (\"Notify me when people chat\").",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your subscription state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "subscribed",
                    "subscribers"
                  ],
                  "properties": {
                    "subscribed": {
                      "type": "boolean"
                    },
                    "subscribers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "membership_id",
                          "name"
                        ],
                        "properties": {
                          "membership_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "subscribeRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Subscribe yourself",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscribed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "subscribed"
                  ],
                  "properties": {
                    "subscribed": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "unsubscribeRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Unsubscribe yourself",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unsubscribed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "subscribed"
                  ],
                  "properties": {
                    "subscribed": {
                      "type": "boolean",
                      "const": false
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/timesheet": {
      "get": {
        "operationId": "listRecordingTimesheet",
        "tags": [
          "Timesheets"
        ],
        "summary": "An item's timesheet",
        "description": "One item's counted entries, newest day first. Items that take time: `todo`, `message`, `document`, `upload`, `card` and `calendar_event`; anything else is a 404. The project's timesheet id (an entry's `parent.id` when `parent.type` is `timesheet`) lists the time on the project itself. A repeating event's timesheet is one day's: pass `occurrence`. An item in the trash lists nothing until it's restored. 404 while the project's Timesheet is off, for an event on the account calendar, and for clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "occurrence",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Required for a repeating event: the day"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "1-based page number"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "description": "Page size, default and cap 100"
          }
        ],
        "responses": {
          "200": {
            "description": "Entries, newest day first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimesheetEntry"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "A repeating event without `occurrence`, or `occurrence` on one that doesn't repeat (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/timesheet/csv": {
      "get": {
        "operationId": "downloadRecordingTimesheetCsv",
        "tags": [
          "Timesheets"
        ],
        "summary": "An item's timesheet as CSV",
        "description": "One to-do's, message's, document's, file's, card's or event's timesheet as a CSV file, unpaginated: a repeating event's day with `occurrence`, or the project's own time when the id is its timesheet. Team only: clients receive 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "occurrence",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Required for a repeating event: the day"
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV file (Content-Disposition: attachment; filename=\"timesheet-YYYY-MM-DD.csv\", dated the caller's today)",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "UTF-8 with no BOM, LF line endings and RFC 4180 quoting. Header `Date,Person,Hours,Regular,Overtime,Project,Item,Notes,Created` (then `Status` while approvals are on), then one row per counted entry, newest day first, then newest logged. No totals row. Hours is the combined total; Regular and Overtime are its two portions, all as decimal strings (\"2.0\", \"1.5\", \"0.12\"); Item is \"{Type}: {title}\" (To-do, Message, Document, File, Card or Event), empty for time on the project itself; Created is ISO 8601 with the caller's UTC offset. A Person, Project, Item or Notes value that starts with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading apostrophe, so spreadsheets read it as text, never as a formula. While approvals are on, a last `Status` column says where each entry's week stands: Approved, Submitted, Changed, Rejected or Not submitted."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/timesheet/entries": {
      "post": {
        "operationId": "createRecordingTimesheetEntry",
        "tags": [
          "Timesheets"
        ],
        "summary": "Log time on an item",
        "description": "The item must be active and one that takes time (to-do, message, document, file, card or event) in a project with its Timesheet on; the project's timesheet id logs time on the project itself. Each day of a repeating event is its own timesheet: pass `occurrence`, and the entry's `parent` is the recording holding that day (the series, a part of it, or the day split off on its own) with `occurrence_date` set. Events on the account calendar take no time. Writes no activity and sends no notification.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "required": [
                      "date",
                      "hours"
                    ],
                    "properties": {
                      "date": {
                        "type": "string",
                        "format": "date",
                        "description": "The day the time was spent; future days are accepted"
                      },
                      "hours": {
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ],
                        "description": "Combined regular and overtime hours, like 1.5 or 1:30. Must be positive; at most 24 hours a person a day across entries."
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 1000,
                        "description": "Notes"
                      },
                      "person_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Whose time (a membership id); defaults to the caller. Admins and owners may name anyone on the project's team; members only themselves (403 otherwise)"
                      },
                      "overtime_hours": {
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ],
                        "description": "Overtime portion of the combined hours, like 1.5 or 1:30. Must be between zero and hours. Defaults to zero on create; omitted on update, preserves existing overtime up to the new total."
                      }
                    }
                  },
                  {
                    "type": "object",
                    "properties": {
                      "occurrence": {
                        "type": "string",
                        "format": "date",
                        "description": "Required for a repeating event: the day (the occurrence key from `?occurrence=` links). A day since split off the series is still that day"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetEntry"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation (code `invalid`: hours not like 1.5 or 1:30, zero hours, a bad day, a repeating event without `occurrence`, a person who isn't a non-client member of the project; archived and trashed items take no new time) or the 24-hour daily cap (code `daily_cap`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/uploads": {
      "get": {
        "operationId": "listChatUploads",
        "tags": [
          "Files"
        ],
        "summary": "List files posted into a chat room or dm",
        "description": "Every file posted into the conversation, newest first. Chat rooms and dms only: any other recording type returns 404. Folder files are listed via GET /recordings/{folderId}/children?type=upload.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A chat or dm recording id"
          }
        ],
        "responses": {
          "200": {
            "description": "The files, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "filename",
                      "byte_size",
                      "content_type",
                      "posted_at"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "The upload recording's id"
                      },
                      "title": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "filename": {
                        "type": "string"
                      },
                      "byte_size": {
                        "type": "integer"
                      },
                      "content_type": {
                        "type": "string"
                      },
                      "posted_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "posted_by": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createUpload",
        "tags": [
          "Files"
        ],
        "summary": "Upload a file",
        "description": "Multipart upload into a project folder (or a dm, for files posted into a conversation). `file` is required and capped at 50 MB. `notify` follows the M5 semantics: \"everyone\", \"none\", or a JSON array of membership ids; omitted means silent, so rich-text attachments never fan out. `visible_to_clients` is the upload card's \"Who should see this?\" for a file posted at the top of Docs & Files. Returns 402 when the plan's storage cap is reached. Rename later via PATCH /recordings/{id} with `base_name` (the extension is kept).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The destination folder (or dm) recording id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The file bytes, up to 50 MB, non-empty"
                  },
                  "description_html": {
                    "type": "string",
                    "description": "Rich notes, sanitized server-side"
                  },
                  "notify": {
                    "type": "string",
                    "description": "\"everyone\", \"none\", or a JSON array of membership ids; omitted means nobody is notified"
                  },
                  "visible_to_clients": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "description": "Who can see a file posted at the top of Docs & Files: \"true\" shares it with the project's clients (only while the project works with clients), \"false\" keeps it to the team. Omitted with `notify`, the project's sharing default applies; omitted without `notify` (a rich-text attachment), the file stays team-only, since a client reaches it through the shared post that embeds it. A file inside a folder, or in a dm, follows its parent whatever is sent"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created upload recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/visibility": {
      "get": {
        "operationId": "getRecordingVisibility",
        "tags": [
          "Recordings"
        ],
        "summary": "Who can see a recording",
        "description": "Where the recording stands with its project's client, as every page's visibility band shows it: `own` (set on this recording: share or hide it here), `inherited` (it follows `from`, its anchor: a to-do its list, a card its board, a doc its folder, an answer its question, a comment its thread), `held` (`from` is shared but this part is held back; sharing it re-joins the anchor), `always` (the Clients tool) or `never` (loose to-dos), or `tool` for a tool's own page (the Message Board, To-dos, Docs & Files root, calendar, Check-ins), which holds anchors and carries no setting. `visible` is whether the client can open it. The team's: a client caller gets a 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The recording's standing with the client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientVisibility"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "setRecordingVisibility",
        "tags": [
          "Recordings"
        ],
        "summary": "Toggle client visibility",
        "description": "Shares or hides an anchor with its project's client, with its whole subtree (comments, and for a list, folder or board everything in it). Only an anchor carries a setting (a message, a to-do list, a doc, file, link or folder at the top of Docs & Files, an event, a check-in question, a progress or health update, Chat, the board): changing anything that follows an anchor is a 403 whose message names the anchor (GET this path for it). Also 403: loose to-dos (never shared), the Clients tool (always shared), and sharing in a project that doesn't work with clients. The one change a child may make is re-joining a shared anchor it was held back from (`held`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "visible_to_clients"
                ],
                "properties": {
                  "visible_to_clients": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/{recordingId}/watch": {
      "get": {
        "operationId": "getWatch",
        "tags": [
          "Recordings"
        ],
        "summary": "Check your watch",
        "description": "Personal \"notify me when new to-dos are added to this list\": watch a todolist (covers its groups) or the todos container (loose to-dos). New to-dos fan out a todo_added notification to watchers.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your watch state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "watching"
                  ],
                  "properties": {
                    "watching": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "watchRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Watch for new to-dos",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Watching",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "watching"
                  ],
                  "properties": {
                    "watching": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "unwatchRecording",
        "tags": [
          "Recordings"
        ],
        "summary": "Stop watching",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "No longer watching",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "watching"
                  ],
                  "properties": {
                    "watching": {
                      "type": "boolean",
                      "const": false
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/recordings/batch": {
      "post": {
        "operationId": "batchRecordings",
        "tags": [
          "Recordings"
        ],
        "summary": "Apply one bulk verb to a selection",
        "description": "One round trip applies `action` to every id, in order; every id passes the same per-recording guards as the single endpoints, and missing per-action fields return 422. Actions: `assign` REPLACES each recording's assignee set (`assignee_ids: []` removes all assignments); `due` sets a day, a `starts_on`..`due_on` range, or both null for no due date; `shift_dates` moves `starts_on` AND `due_on` forward by `amount` x `unit`, skipping undated recordings (`count` = rows actually shifted); `move` re-parents (arrival order = `ids` order); `copy` deep-copies (returns the copies' `ids`); `status` archives, trashes, or restores; `color` tints Docs & Files items (`white` clears); `group` creates a group in the selection's list, moves the selection in, and returns `group_id`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids",
                  "action"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "assign",
                      "due",
                      "shift_dates",
                      "move",
                      "copy",
                      "status",
                      "color",
                      "group"
                    ]
                  },
                  "assignee_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "assign: the replacement assignee set ([] removes all assignments)"
                  },
                  "due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "due: a day, or the end of a starts_on..due_on range; null clears"
                  },
                  "starts_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "description": "due: range start; must not be after due_on"
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30,
                    "description": "shift_dates: how far forward"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "days",
                      "weekdays",
                      "weeks",
                      "months"
                    ],
                    "description": "shift_dates: the step unit"
                  },
                  "parent_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "move / copy: the destination container"
                  },
                  "include_comments": {
                    "type": "boolean",
                    "description": "copy: bring each thread along (original authors and timestamps)"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "archived",
                      "trashed"
                    ],
                    "description": "status: the target lifecycle state"
                  },
                  "color": {
                    "type": "string",
                    "enum": [
                      "white",
                      "red",
                      "orange",
                      "yellow",
                      "green",
                      "blue",
                      "aqua",
                      "purple",
                      "gray",
                      "pink",
                      "brown"
                    ],
                    "description": "color: Docs & Files tint (white clears); also the optional group color"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 10000,
                    "description": "group: the new group's name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the bulk verb touched",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordingBatchResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/calendar/upcoming": {
      "get": {
        "operationId": "getUpcomingCalendarReport",
        "tags": [
          "Calendar"
        ],
        "summary": "Upcoming dates report",
        "description": "Everything dated inside the window: one-time events, expanded recurring occurrences, and dated open assignables (to-dos, cards, and card steps, which the calendar's own task arm lacks). Both window parameters are required; a missing, malformed, or inverted window is a 400. Team only: clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "window_starts_on",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "window_ends_on",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The window's report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpcomingReport"
                }
              }
            }
          },
          "400": {
            "description": "Missing, malformed, or inverted window (code bad_request)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/health": {
      "get": {
        "operationId": "getHealthReport",
        "tags": [
          "Search & reports"
        ],
        "summary": "Org-wide project health gauges",
        "description": "Every accessible active project with its health gauge on and a needle set, most recently updated first. Team only: clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The gauges",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "gauges"
                  ],
                  "properties": {
                    "gauges": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HealthReportGauge"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/timesheet": {
      "get": {
        "operationId": "getTimesheetReport",
        "tags": [
          "Timesheets"
        ],
        "summary": "The Timesheets report",
        "description": "Every counted entry in a date range across the projects the caller can see: a flat list, newest day first, then newest logged. Not paginated; the range is the limit. An entry counts while its item exists: an item in the trash takes its hours out until it's restored, and an event parked on the account calendar takes them out until it moves back into a project. All projects means active and archived ones (templates and the sample project only when named in `project_id`), including projects whose Timesheet was since switched off, plus absence: everyone's for owners and admins, a member's own for them. `project_id` names one project and leaves absence out. While approvals are on, `status` reads one approval status (default `approved`: approved and unchanged since; `changed`: submitted or approved, then changed; rejected weeks stay out until resubmitted); it's ignored while they're off. Team only: clients receive 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "With end_date; default the last month, ending today in the caller's zone. At most 366 days"
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "With start_date, inclusive"
          },
          {
            "name": "person_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "One person (membership id); default everyone"
          },
          {
            "name": "project_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "One project; default all projects plus absence"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "approved",
                "submitted",
                "changed",
                "not_submitted"
              ]
            },
            "description": "Approvals on only; default approved (and unchanged since)"
          }
        ],
        "responses": {
          "200": {
            "description": "Entries, newest day first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimesheetEntry"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "start_date without end_date (or the reverse), end before start, or a range over 366 days (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/timesheet/csv": {
      "get": {
        "operationId": "downloadTimesheetReportCsv",
        "tags": [
          "Timesheets"
        ],
        "summary": "The Timesheets report as CSV",
        "description": "The Timesheets report as a CSV file: the same parameters, defaults and entries as `getTimesheetReport`, one flat row per entry, newest day first, then newest logged. Absence rows carry Project \"Absence\" and the absence type's name as Item. Team only: clients receive 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "With end_date; default the last month, ending today in the caller's zone. At most 366 days"
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "With start_date, inclusive"
          },
          {
            "name": "person_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "One person (membership id); default everyone"
          },
          {
            "name": "project_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "One project; default all projects plus absence"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "approved",
                "submitted",
                "changed",
                "not_submitted"
              ]
            },
            "description": "Approvals on only; default approved (and unchanged since)"
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV file (Content-Disposition: attachment; filename=\"timesheet-YYYY-MM-DD.csv\", dated the caller's today)",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "UTF-8 with no BOM, LF line endings and RFC 4180 quoting. Header `Date,Person,Hours,Regular,Overtime,Project,Item,Notes,Created` (then `Status` while approvals are on), then one row per counted entry, newest day first, then newest logged. No totals row. Hours is the combined total; Regular and Overtime are its two portions, all as decimal strings (\"2.0\", \"1.5\", \"0.12\"); Item is \"{Type}: {title}\" (To-do, Message, Document, File, Card or Event), empty for time on the project itself, and the absence type for absence (Project \"Absence\"); Created is ISO 8601 with the caller's UTC offset. A Person, Project, Item or Notes value that starts with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading apostrophe, so spreadsheets read it as text, never as a formula. While approvals are on, a last `Status` column says where each entry's week stands: Approved, Submitted, Changed, Rejected or Not submitted."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "start_date without end_date (or the reverse), end before start, or a range over 366 days (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/todos/assigned": {
      "get": {
        "operationId": "listAssignablePeople",
        "tags": [
          "Search & reports"
        ],
        "summary": "Everyone who can have work assigned",
        "description": "The people directory the assigned-work report starts from; feed a `membership_id` into /reports/todos/assigned/{membershipId} for that person's open work. Team only: clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The assignable people",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "people"
                  ],
                  "properties": {
                    "people": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReportPerson"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/todos/assigned/{membershipId}": {
      "get": {
        "operationId": "getPersonAssignments",
        "tags": [
          "Search & reports"
        ],
        "summary": "One person's open assignments",
        "description": "That person's open to-dos, cards and card steps across the projects the caller can see (schedule events they are invited to are not assignments). `group_by=bucket` (default) sorts by project, `group_by=date` by due date; the response echoes the grouping in `grouped_by`. Team only: clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "group_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "bucket",
                "date"
              ],
              "default": "bucket"
            },
            "description": "`bucket`: by project; `date`: by due date"
          }
        ],
        "responses": {
          "200": {
            "description": "The person and their open work",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssignedReport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/reports/todos/overdue": {
      "get": {
        "operationId": "getOverdueTodosReport",
        "tags": [
          "Search & reports"
        ],
        "summary": "All overdue to-dos, by lateness",
        "description": "Every overdue open to-do across accessible projects, grouped into four lateness buckets. Each row carries `days_late` and its assignee chips. Team only: clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The four lateness buckets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OverdueReport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/roadmap/markers": {
      "get": {
        "operationId": "listRoadmapMarkers",
        "tags": [
          "Projects"
        ],
        "summary": "List roadmap markers",
        "description": "Org-wide roadmap markers in date order (bc3 lineup_markers conventions). Team-readable; clients receive 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Markers in date order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "markers"
                  ],
                  "properties": {
                    "markers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RoadmapMarker"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createRoadmapMarker",
        "tags": [
          "Projects"
        ],
        "summary": "Create a roadmap marker",
        "description": "Owners and admins only; others receive 403. Both fields are required; a missing or invalid name or date is a 400.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "date"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created marker",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "marker"
                  ],
                  "properties": {
                    "marker": {
                      "$ref": "#/components/schemas/RoadmapMarker"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid name or date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/roadmap/markers/{markerId}": {
      "put": {
        "operationId": "updateRoadmapMarker",
        "tags": [
          "Projects"
        ],
        "summary": "Rename or move a marker",
        "description": "Owners and admins only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "markerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated marker",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "marker"
                  ],
                  "properties": {
                    "marker": {
                      "$ref": "#/components/schemas/RoadmapMarker"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid name or date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "deleteRoadmapMarker",
        "tags": [
          "Projects"
        ],
        "summary": "Delete a marker",
        "description": "Permanent: markers have no trash lifecycle. Owners and admins only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "markerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/search": {
      "get": {
        "operationId": "search",
        "tags": [
          "Search & reports"
        ],
        "summary": "Full-text search",
        "description": "Postgres FTS with word-prefix matching (as-you-type queries find whole words), relevance-ranked with a recency boost. Results honor your visibility: accessible projects only, client rules applied. Rows are enriched with project, creator, and parent context plus `excerpt`/`title_excerpt`, tag-stripped text with matches wrapped in `<em>`. Fetch valid `type`/`file_type` values from /search/metadata rather than hardcoding.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "project_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "One recording type; valid keys come from /search/metadata"
          },
          {
            "name": "creator_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Created on or after this date"
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Created on or before this date"
          },
          {
            "name": "archived",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "1",
                "0"
              ]
            },
            "description": "Include archived projects and recordings"
          },
          {
            "name": "exclude_chat",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "1",
                "0"
              ]
            },
            "description": "Drop chat and DM lines"
          },
          {
            "name": "file_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "image",
                "audio",
                "video",
                "pdf"
              ]
            },
            "description": "Narrow upload results by kind"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "last_year",
                "forever"
              ]
            },
            "description": "`last_year`: a 12-month window"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "relevance",
                "recent"
              ],
              "default": "relevance"
            }
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching recordings (default page size 50); while more exist, `Link: rel=\"next\"` names the next page",
            "headers": {
              "Link": {
                "$ref": "#/components/headers/Link"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SearchResult"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/search/metadata": {
      "get": {
        "operationId": "getSearchMetadata",
        "tags": [
          "Search & reports"
        ],
        "summary": "Valid search filter options",
        "description": "The `type` and `file_type` filter vocabularies for /search, as key/label pairs (a null key means no filter). Fetch these rather than hardcoding: the vocabulary grows with the product.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Filter options",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchMetadata"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/settings": {
      "get": {
        "operationId": "getOrgSettings",
        "tags": [
          "Organizations"
        ],
        "summary": "Read the org settings",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "The settings document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateOrgSettings",
        "tags": [
          "Organizations"
        ],
        "summary": "Update the org settings",
        "description": "Tool names, history retention, voice notes, the timesheet week start, and the five governance flags: admin and up. `require_two_factor` is owner-only. Members receive 403. Governance flags bite at the guarded writes, not here: restricted project or people edits, moves, archives, and trashes answer 403 for non-creator members; public-link creation answers 403 for non-admins; comment and chat edits answer 403 for everyone (and deletes for non-admins) after 15 minutes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "require_two_factor": {
                    "type": "boolean",
                    "description": "Owner-only. When on, non-client members without TOTP are funneled into 2FA setup at org entry; clients are exempt"
                  },
                  "history_retention_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 3650,
                    "description": "null keeps chat and DM history forever; a number hard-deletes older lines, permanently"
                  },
                  "tool_names": {
                    "type": "object",
                    "description": "Per-tool display-name overrides applied account-wide",
                    "additionalProperties": false,
                    "properties": {
                      "message_board": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "todos": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "folder": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "calendar": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "board": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "chat": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "check_ins": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "clients": {
                        "type": "string",
                        "maxLength": 40
                      }
                    }
                  },
                  "voice_notes_enabled": {
                    "type": "boolean"
                  },
                  "restrict_project_edits": {
                    "type": "boolean"
                  },
                  "restrict_people_edits": {
                    "type": "boolean"
                  },
                  "restrict_content_actions": {
                    "type": "boolean"
                  },
                  "restrict_public_links": {
                    "type": "boolean"
                  },
                  "limit_comment_editing": {
                    "type": "boolean"
                  },
                  "timesheet_week_start": {
                    "type": "string",
                    "enum": [
                      "sunday",
                      "monday",
                      "tuesday",
                      "wednesday",
                      "thursday",
                      "friday",
                      "saturday"
                    ],
                    "description": "Owners and admins: the day the weekly timesheet starts. Rows already added to a week show in the new week that holds their day"
                  },
                  "timesheet_approvals_enabled": {
                    "type": "boolean",
                    "description": "Owners and admins: turn timesheet approvals on or off. Off hides every week's status and keeps it; on shows them again"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fresh settings document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/support": {
      "post": {
        "operationId": "createSupportTicket",
        "tags": [
          "Organizations"
        ],
        "summary": "Open a support ticket",
        "description": "The in-app support dialog's endpoint: pick a topic and write a message. The caller's email, name, and workspace context are attached server-side and forwarded to Thicket support, which acknowledges by email. When forwarding fails the error points at support@thickethq.com instead.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "topic",
                  "message"
                ],
                "properties": {
                  "topic": {
                    "type": "string",
                    "enum": [
                      "broken",
                      "login",
                      "signup",
                      "request",
                      "billing",
                      "email",
                      "confused"
                    ]
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ticket created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ticket"
                  ],
                  "properties": {
                    "ticket": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The ticket reference"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "502": {
            "description": "The ticket could not be forwarded (code support_failed); email support@thickethq.com instead",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "summary": "List project templates",
        "description": "Active templates in name order; `?status` lists archived or trashed instead. Templates are not projects to the rest of the API: they never appear in `/projects` listings, do not count against the plan's project limit, and stay out of activity feeds.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "trashed"
              ]
            },
            "description": "Defaults to active"
          }
        ],
        "responses": {
          "200": {
            "description": "Templates in name order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Create a template",
        "description": "With `project_id`: snapshot that project's structure (tools, lists, groups, to-dos, pinned messages, folders and docs, columns, questions); completed to-dos reset to incomplete and cards return to Triage. Without it: an empty template to build up in the template editor, and `name` is required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The project to snapshot; omit for an empty template"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Required when project_id is omitted. Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "include_assignments": {
                    "type": "boolean"
                  },
                  "include_comments": {
                    "type": "boolean"
                  },
                  "keep_client_visibility": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created template",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/templates/{templateId}": {
      "get": {
        "operationId": "getTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Get a template",
        "description": "The template plus its `tools`, the same shape as a project GET; `dock` is the same deprecated alias.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The template with its tools",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Update a template",
        "description": "`clients_enabled` is the template's Work-with-clients flag; projects made from it inherit it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 150,
                    "description": "Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted. Resending the current name is always accepted, even one set before this rule."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "clients_enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "operationId": "deleteTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Trash a template",
        "description": "Moves the template to the trash.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Trashed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/templates/{templateId}/use": {
      "post": {
        "operationId": "useTemplate",
        "tags": [
          "Templates"
        ],
        "summary": "Start a project from a template",
        "description": "Creates a real project from the template. It inherits the template's client setting; dates shift relative to today (anchored on the template's `starts_on`, else its save date); people assigned in the template join the new project. Instantiating enforces the plan's project limit (402) even though templates themselves never count against it. Archived templates cannot instantiate (404).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "all_access": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet-entries/{entryId}": {
      "get": {
        "operationId": "getTimesheetEntry",
        "tags": [
          "Timesheets"
        ],
        "summary": "Get a time entry",
        "description": "An entry while it counts: its item in the trash, or its event on the account calendar, is a 404 until it's back. Time in a project reads for anyone on it, even after its Timesheet is switched off; absence reads only for its person, admins and owners. Clients receive 404.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetEntry"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateTimesheetEntry",
        "tags": [
          "Timesheets"
        ],
        "summary": "Update a time entry",
        "description": "Send only what changes: the day, hours, notes or person; what the time is on never changes. Members change their own time; admins and owners anyone's. A move to another day or person checks the 24-hour cap on the new one; editing only the notes never does.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "format": "date"
                  },
                  "hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Combined regular and overtime hours, like 1.5 or 1:30. Must be positive; at most 24 hours a person a day across entries."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "person_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "overtime_hours": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ],
                    "description": "Overtime portion of the combined hours, like 1.5 or 1:30. Must be between zero and hours. Defaults to zero on create; omitted on update, preserves existing overtime up to the new total."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetEntry"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Validation (code `invalid`: hours not like 1.5 or 1:30, zero hours, a bad day, a repeating event without `occurrence`, a person who isn't a non-client member of the project; archived and trashed items take no new time) or the 24-hour daily cap (code `daily_cap`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTimesheetEntry",
        "tags": [
          "Timesheets"
        ],
        "summary": "Delete a time entry",
        "description": "Permanent: an entry has no trash of its own (it goes there only with its item, and comes back with it). Members delete their own time; admins and owners anyone's.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet/absence-types": {
      "get": {
        "operationId": "listTimesheetAbsenceTypes",
        "tags": [
          "Timesheets"
        ],
        "summary": "List absence types",
        "description": "The org's kinds of time off, in order. Every org starts with Vacation, Sick leave, Bereavement and Statutory holiday.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "include_archived",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "`true` adds the removed ones"
          }
        ],
        "responses": {
          "200": {
            "description": "The absence types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TimesheetAbsenceType"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createTimesheetAbsenceType",
        "tags": [
          "Timesheets"
        ],
        "summary": "Add an absence type",
        "description": "Owners and admins. It goes at the end of the list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The absence type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetAbsenceType"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "An empty name, one over 60 characters, or one an active type already has (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet/absence-types/{absenceTypeId}": {
      "patch": {
        "operationId": "updateTimesheetAbsenceType",
        "tags": [
          "Timesheets"
        ],
        "summary": "Rename, reorder, remove or restore an absence type",
        "description": "Owners and admins. Removing (`archived: true`) keeps the hours already logged under its name; it takes no new time and leaves the weekly timesheet's Absence rows. Restoring (`archived: false`) puts it back at the end of the list.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "absenceTypeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "position": {
                    "type": "number",
                    "description": "Types sort by position; any number between two neighbors' puts it between them"
                  },
                  "archived": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The absence type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetAbsenceType"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "An empty name, one over 60 characters, or one an active type already has (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet/approvals": {
      "get": {
        "operationId": "getTimesheetApprovals",
        "tags": [
          "Timesheets"
        ],
        "summary": "The approvals page for one week",
        "description": "Owners and admins, while approvals are on: for the week holding `week`, a row per person with time or a submission (their hours in projects the caller can see, the status as read now, who submitted and decided it), the team's people with neither, and every submitted or changed week from any other period still waiting on a decision, oldest first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "week",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Any day in the week; default today in the caller's zone"
          }
        ],
        "responses": {
          "200": {
            "description": "The week's approvals",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetApprovals"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Approvals are off (code `approvals_off`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet/approvals/{membershipId}/{weekStart}/approve": {
      "post": {
        "operationId": "approveTimesheetWeek",
        "tags": [
          "Timesheets"
        ],
        "summary": "Approve a submitted week",
        "description": "Owners and admins, while approvals are on. Approves the week as its hours are now: a changed week's current hours become the approved ones. Approving your own week marks it `self_approved`. Approving an approved week changes nothing. Quiet: notifies no one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Whose week"
          },
          {
            "name": "weekStart",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The submitted week's first day"
          }
        ],
        "responses": {
          "200": {
            "description": "The week's approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetWeekApproval"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Approvals are off (code `approvals_off`), or the week was never submitted, or was rejected and waits for its person to resubmit it (code `week_state`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/timesheet/approvals/{membershipId}/{weekStart}/reject": {
      "post": {
        "operationId": "rejectTimesheetWeek",
        "tags": [
          "Timesheets"
        ],
        "summary": "Reject a week, with a reason",
        "description": "Owners and admins, while approvals are on. Sends a submitted, changed or approved week back: it stays out of the report until it's resubmitted, and its person is notified with the reason, in the tray (action `timesheet_rejected`) and by email.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "membershipId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Whose week"
          },
          {
            "name": "weekStart",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "The submitted week's first day"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Why, for the person: required, at most 1,000 characters once trimmed"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The week's approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimesheetWeekApproval"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Approvals are off (code `approvals_off`), or the week was never submitted or is already rejected (code `week_state`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "No reason, or one over 1,000 characters (code `invalid`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{orgSlug}/trash": {
      "get": {
        "operationId": "listTrash",
        "tags": [
          "Trash & admin"
        ],
        "summary": "The account-wide trash",
        "description": "Owner only, oldest first: everything deleted across all projects. Items purge permanently after 30 days. Members and clients receive 403; the project-scoped trash is GET /projects/{projectId}/trash.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          }
        ],
        "responses": {
          "200": {
            "description": "Trashed items, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TrashedItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/trash/{recordingId}": {
      "delete": {
        "operationId": "destroyTrashedRecording",
        "tags": [
          "Trash & admin"
        ],
        "summary": "Permanently delete a trashed item",
        "description": "Owner only and irreversible: stored files and the row go, with the subtree, assignments, subscriptions, notifications, events, cheers, bookmarks, and links cascading away.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Destroyed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/trash/{recordingId}/restore": {
      "post": {
        "operationId": "restoreTrashedRecording",
        "tags": [
          "Trash & admin"
        ],
        "summary": "Restore a trashed item",
        "description": "Owner trash-page restore: brings back the item and exactly the subtree trashed with it in the same batch (independently trashed children stay trashed).",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The restored recording",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/uploads/{recordingId}/download": {
      "get": {
        "operationId": "downloadUpload",
        "tags": [
          "Files"
        ],
        "summary": "Download a file",
        "description": "Redirects (302) to a short-lived presigned URL with attachment disposition; follow the redirect to get the bytes. The presigned URL expires quickly, so request a fresh one per download rather than storing it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The upload recording's id"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the short-lived presigned download URL",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/uploads/{recordingId}/file": {
      "get": {
        "operationId": "getUploadFile",
        "tags": [
          "Files"
        ],
        "summary": "Serve a file inline",
        "description": "The stable same-origin route rich-text `img` and `audio` srcs point at, so stored HTML never carries expiring URLs. Access is re-checked on every request, then 302 to a short-lived presigned URL with inline disposition.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The upload recording's id"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the short-lived presigned inline URL",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/uploads/{recordingId}/versions": {
      "get": {
        "operationId": "listUploadVersions",
        "tags": [
          "Files"
        ],
        "summary": "List a file's versions",
        "description": "The current version plus every past one, newest first: every past version is kept on file.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The upload recording's id"
          }
        ],
        "responses": {
          "200": {
            "description": "Current file plus kept versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "current",
                    "versions"
                  ],
                  "properties": {
                    "current": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "required": [
                        "filename",
                        "byte_size",
                        "content_type"
                      ],
                      "properties": {
                        "filename": {
                          "type": "string"
                        },
                        "byte_size": {
                          "type": "integer"
                        },
                        "content_type": {
                          "type": "string"
                        }
                      }
                    },
                    "versions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UploadVersion"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "replaceUploadVersion",
        "tags": [
          "Files"
        ],
        "summary": "Replace the file with a new version",
        "description": "Multipart body with `file` (up to 50 MB). Same recording keeps its URL, comments, and subscribers; the outgoing version moves to the kept list and the title and size flip to the new file. `notify` picks who hears about the new version; omitted defaults to the project audience. Returns 402 when the plan's storage cap is reached.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The upload recording's id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The replacement file bytes, up to 50 MB, non-empty"
                  },
                  "notify": {
                    "type": "string",
                    "description": "\"everyone\", \"none\", or a JSON array of membership ids; omitted defaults to the project audience"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The upload recording, now pointing at the new version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Recording"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/{orgSlug}/uploads/{recordingId}/versions/{versionId}/download": {
      "get": {
        "operationId": "downloadUploadVersion",
        "tags": [
          "Files"
        ],
        "summary": "Download a past version",
        "description": "Redirects (302) to a short-lived presigned URL for a past version of the file; follow the redirect to get the bytes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/orgSlug"
          },
          {
            "name": "recordingId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The upload recording's id"
          },
          {
            "name": "versionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A version id from the versions listing"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the short-lived presigned download URL",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/authorization": {
      "get": {
        "operationId": "getAuthorization",
        "tags": [
          "Authorization"
        ],
        "summary": "Introspect the credential",
        "description": "Who am I, which organizations can I reach, what may this credential do. Make this your integration's first call; each organization's `href` is its API base URL.",
        "responses": {
          "200": {
            "description": "The credential's authorization document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Authorization"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/cli/authorizations": {
      "post": {
        "operationId": "createCliAuthorization",
        "tags": [
          "Authorization"
        ],
        "summary": "Approve a CLI sign-in (session-only)",
        "description": "Legacy CLI compatibility. CLI 0.4 and later use OAuth authorization_code with PKCE at /api/oauth/token, targeting the /api/v1 resource. The approve half of `thicket auth login`: records a PKCE-style grant for the presented challenge and returns a single-use code (10-minute expiry) for the CLI's loopback callback. Session-only like token minting: bearer calls receive 403 `session_required`. The code itself is not a credential; it only pays out at the exchange to whoever holds the matching `code_verifier`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "challenge",
                  "device_name"
                ],
                "properties": {
                  "challenge": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$",
                    "description": "base64url(SHA-256(code_verifier)) — the PKCE S256 transform"
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "read",
                      "full"
                    ],
                    "default": "read"
                  },
                  "device_name": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Shown on the approve page and baked into the minted token's name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The single-use authorization code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "expires_at"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        },
        "deprecated": true
      }
    },
    "/api/v1/cli/token": {
      "post": {
        "operationId": "exchangeCliAuthorization",
        "tags": [
          "Authorization"
        ],
        "summary": "Exchange a CLI authorization code for a token",
        "security": [],
        "description": "Legacy CLI compatibility. CLI 0.4 and later use OAuth authorization_code with PKCE at /api/oauth/token, targeting the /api/v1 resource. The exchange half of `thicket auth login`. Anonymous by design: the code plus the PKCE `code_verifier` are the credential. Any exchange attempt burns the code (single use), and refusals are indistinguishable (unknown, expired, used, or mismatched verifier all answer 401 `invalid_grant`). The response's `token` field is the only time the minted personal access token exists in plaintext. Rate-limited per caller address; 429 carries Retry-After.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "code_verifier"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string",
                    "minLength": 43,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The minted token",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PersonalAccessToken"
                    },
                    {
                      "type": "object",
                      "required": [
                        "token"
                      ],
                      "properties": {
                        "token": {
                          "type": "string",
                          "description": "The secret; shown exactly once"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "deprecated": true
      }
    },
    "/api/v1/me/connected-apps": {
      "get": {
        "operationId": "listConnectedApps",
        "tags": [
          "Authorization"
        ],
        "summary": "List connected apps",
        "description": "The caller's OAuth grants (the MCP connector's Connected apps surface), newest first. Session-only: bearer callers get 403 `session_required`, so a leaked token cannot enumerate the account's standing grants.",
        "responses": {
          "200": {
            "description": "Grants, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ConnectedApp"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/me/connected-apps/{grantId}": {
      "delete": {
        "operationId": "revokeConnectedApp",
        "tags": [
          "Authorization"
        ],
        "summary": "Disconnect an app",
        "description": "Revokes the OAuth grant: its access and refresh tokens die immediately. Session-only.",
        "parameters": [
          {
            "name": "grantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/me/preferences": {
      "get": {
        "operationId": "getPreferences",
        "tags": [
          "Me"
        ],
        "summary": "Your personal preferences",
        "description": "User-scoped, not org-scoped: one set of preferences follows you across organizations.",
        "responses": {
          "200": {
            "description": "The full preferences document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "patch": {
        "operationId": "updatePreferences",
        "tags": [
          "Me"
        ],
        "summary": "Update preferences",
        "description": "Send only the fields to change; nullable fields accept null to clear. Returns the full updated document.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Preferences"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The full updated document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preferences"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/me/push-devices": {
      "put": {
        "operationId": "registerPushDevice",
        "tags": [
          "Me"
        ],
        "summary": "Register this device for mobile push",
        "description": "Upserts the device's push token; the app calls it on start and after login, refreshing `last_seen_at`. User-scoped like preferences: notifications follow the person across orgs. Registering a token claims it from any previous signer, so on a shared device the previous user's notifications stop arriving. A native registration (`kind: apns`) supersedes the same person's Expo devices on that platform, so a phone that updated from the 1.x app never receives the same push twice. `expo_push_token` is the deprecated 1.x field, equivalent to `{token, kind: \"expo\"}`. A token that does not match its kind (an Expo token must be `ExponentPushToken[…]`, an APNs token hex, on an iOS device) is a 422. Dead tokens are pruned automatically when the transport reports them (Expo DeviceNotRegistered, APNs 410 Unregistered or BadDeviceToken).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "maxLength": 4096,
                    "description": "The transport's token: `ExponentPushToken[…]` for `expo`, the hex device token for `apns`. Required unless `expo_push_token` is sent."
                  },
                  "kind": {
                    "$ref": "#/components/schemas/PushTokenKind"
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android"
                    ]
                  },
                  "device_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "expo_push_token": {
                    "type": "string",
                    "maxLength": 4096,
                    "deprecated": true,
                    "description": "Deprecated: the 1.x app's field, equivalent to `token` with `kind: \"expo\"`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The registered device",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "kind",
                    "platform",
                    "device_name",
                    "last_seen_at",
                    "expo_push_token"
                  ],
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "kind": {
                      "$ref": "#/components/schemas/PushTokenKind"
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "ios",
                        "android"
                      ]
                    },
                    "device_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "last_seen_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expo_push_token": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "deprecated": true,
                      "description": "Deprecated alias of `token` for Expo devices; null for native tokens."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/me/push-devices/{token}": {
      "delete": {
        "operationId": "unregisterPushDevice",
        "tags": [
          "Me"
        ],
        "summary": "Unregister a push device",
        "description": "Sign-out unregisters the device, whatever the token's kind. Idempotent: an unknown or already-removed token is still a 204.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The device's push token as registered, URL-encoded"
          }
        ],
        "responses": {
          "204": {
            "description": "Unregistered"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/me/push-devices/test": {
      "post": {
        "operationId": "sendTestPush",
        "tags": [
          "Me"
        ],
        "summary": "Send a test push to my devices",
        "description": "Sends one push notification (\"Push notifications are working on this phone.\") to every device the caller has registered, through each device's own transport (Expo or APNs): the self-serve end-to-end check behind the app's Notifications settings. `sent: 0` means nothing is registered for this person yet. Dead tokens are pruned and Expo delivery receipts tracked exactly as for a normal fan-out.",
        "responses": {
          "200": {
            "description": "How many devices were addressed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "sent",
                    "devices"
                  ],
                  "properties": {
                    "sent": {
                      "type": "integer",
                      "description": "Devices the push went out to"
                    },
                    "devices": {
                      "type": "integer",
                      "description": "Devices registered before dead tokens were pruned"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/api/v1/me/security": {
      "get": {
        "operationId": "getSecurityState",
        "tags": [
          "Me"
        ],
        "summary": "Your non-secret sign-in security state",
        "description": "Session-only account-scoped enrollment bootstrap that remains reachable while an organization requires 2FA. It reveals only whether 2FA and a password are active plus a human-readable social sign-in label; it never returns linked-account identifiers, secrets, or password material. Personal access tokens receive 403 session_required.",
        "responses": {
          "200": {
            "description": "Security state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SecurityState"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/me/tokens": {
      "get": {
        "operationId": "listTokens",
        "tags": [
          "Authorization"
        ],
        "summary": "List your personal access tokens",
        "description": "Never returns secrets: `token_prefix` and `last_used_at` exist so the list stays recognizable. Session-only: bearer calls receive 403 `session_required`.",
        "responses": {
          "200": {
            "description": "Your tokens, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PersonalAccessToken"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "operationId": "createToken",
        "tags": [
          "Authorization"
        ],
        "summary": "Mint a personal access token",
        "description": "The response's `token` field is the only time the secret exists in plaintext. Session-only: a leaked token must not mint successors.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "read",
                      "full"
                    ],
                    "default": "read",
                    "description": "Defaults to read (GET/HEAD only), matching the CLI; pick full deliberately for writes"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Omit or null: the token does not expire"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The minted token",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PersonalAccessToken"
                    },
                    {
                      "type": "object",
                      "required": [
                        "token"
                      ],
                      "properties": {
                        "token": {
                          "type": "string",
                          "description": "The secret; shown exactly once"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/me/tokens/{tokenId}": {
      "delete": {
        "operationId": "revokeToken",
        "tags": [
          "Authorization"
        ],
        "summary": "Revoke a token",
        "description": "Effective immediately. Session-only.",
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Revoked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/orgs": {
      "get": {
        "operationId": "listOrganizations",
        "tags": [
          "Organizations"
        ],
        "summary": "List your organizations",
        "description": "Every organization the credential's user belongs to, ordered by name. Cancelled-in-grace organizations are excluded (see /api/v1/orgs/cancelled). `sole_owner` means the caller is the org's only active owner, which blocks account deletion. `requires_two_factor_setup` lets interactive clients route to enrollment before opening org data; clients are exempt.",
        "responses": {
          "200": {
            "description": "Your organizations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrgListItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "operationId": "createOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Create an organization",
        "description": "The caller becomes its owner. The slug is generated server-side ({slugified-name}-{6-char suffix}) and the org starts in the same server-decided state as web onboarding: a 30-day Pro trial with no card, plus the seeded Getting Started sample project. Deliberately takes no plan or billing parameter: plan selection is never a client concern.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "description": "Must contain a letter or number in some script. Refused with 422 (invalid) when it carries a link: a scheme such as https://, a www. address, or a domain followed by a path. A bare domain such as Linear.app is a name and is accepted."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new organization, in the orgs-list row shape (role is always owner, sole_owner always true)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrgListItem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "409": {
            "description": "Slug generation collided five times in a row (code conflict, practically unreachable); retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/api/v1/orgs/{orgId}/restore": {
      "post": {
        "operationId": "restoreOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Restore a cancelled organization",
        "description": "Bring a cancelled account back inside its 30-day window (owner only, no body). Non-members and unknown ids alike receive 404: an org id must not be probeable.",
        "parameters": [
          {
            "name": "orgId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Restored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "slug",
                    "name"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "The 30-day window has passed (code restore_window_passed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orgs/cancelled": {
      "get": {
        "operationId": "listCancelledOrganizations",
        "tags": [
          "Organizations"
        ],
        "summary": "List your cancelled-but-restorable organizations",
        "description": "Cancelled organizations still inside their 30-day restore window. `can_restore` is true where the caller is an owner; restoring is POST /api/v1/orgs/{orgId}/restore.",
        "responses": {
          "200": {
            "description": "Cancelled organizations inside the restore window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CancelledOrg"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "personalAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal access token from My settings → API tokens (`thicket_pat_…`). The token acts as its user: same organizations, project access, role, and row-level security as their sign-in. `read`-scope tokens may only GET/HEAD (writes → 403 `read_only_token`). Bearer requests must send a User-Agent naming the app and a contact (URL or email) or they receive 400 `user_agent_required`. The web app's own session cookie is also accepted on every route."
      },
      "sessionToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Better Auth session token presented as a bearer, the native apps' credential. Sign-in responses (`/api/auth/sign-in/email`, `/api/auth/sign-in/social`) carry it in the `set-auth-token` header; send it back as `Authorization: Bearer <token>`. It is the same session a browser holds in its cookie and is treated exactly like one: full scope, no User-Agent requirement, no token rate limit. Told apart from a personal access token by the `thicket_pat_` prefix, which session tokens never carry."
      },
      "oauthBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "OAuth access token bound to https://www.thickethq.com/api/v1, obtained through browser approval with PKCE. thicket.read allows GET/HEAD; thicket.write allows mutations within current user permissions. MCP-bound tokens are not accepted. User-Agent is required. Manage grants in My settings, Connected apps."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every non-2xx response carries this envelope.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable cause: unauthenticated, user_agent_required, forbidden, read_only_token, session_required, limit_reached, not_found, invalid, bad_request, rate_limited, …"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "RecordingType": {
        "type": "string",
        "description": "The polymorphic content vocabulary. Tool containers: message_board, todos, folder, calendar, board, chat, check_ins, clients (and the lazily-created health gauge). Content: everything else.",
        "enum": [
          "message_board",
          "message",
          "comment",
          "todos",
          "todolist",
          "todo",
          "progress_update",
          "folder",
          "document",
          "upload",
          "linked_file",
          "calendar",
          "calendar_event",
          "board",
          "column",
          "card",
          "step",
          "chat",
          "chat_message",
          "check_ins",
          "question",
          "answer",
          "dm",
          "clients",
          "client_approval",
          "client_correspondence",
          "personal_note",
          "health",
          "health_update"
        ]
      },
      "RecordingStatus": {
        "type": "string",
        "enum": [
          "active",
          "archived",
          "trashed",
          "drafted"
        ]
      },
      "Recording": {
        "type": "object",
        "description": "The uniform shape every recording serializes to; type-specific data rides in `meta`. List rows on children endpoints additionally carry creator_name, creator_image, comment_count, last_comment_at, plus per-type riders: chat_message rows their cheers; client_approval and card rows their assignees; card rows step_count/steps_done; column rows card_count; todolist rows todo_open_count/todo_done_count; upload rows byte_size/content_type.",
        "required": [
          "id",
          "type",
          "status",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/RecordingStatus"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sanitized HTML"
          },
          "position": {
            "type": [
              "number",
              "null"
            ]
          },
          "due_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "starts_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "series_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Calendar events split off a repeating event (a day changed on its own, a deleted day in the trash, a \"this and all future\" part): the repeating event. Null otherwise."
          },
          "occurrence_key": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "With `series_id`: the anchor-zone day key of the occurrence this event replaces (a \"this and all future\" part's first day)."
          },
          "completed": {
            "type": "boolean"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_by_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "creator_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "visible_to_clients": {
            "type": "boolean"
          },
          "pinned_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "category_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "approval_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "approved",
              "rejected",
              null
            ]
          },
          "meta": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SearchResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "properties": {
              "project_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "creator_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "parent_title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "byte_size": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "content_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "excerpt": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Tag-stripped text, matches wrapped in <em>"
              },
              "title_excerpt": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "ProjectMember": {
        "type": "object",
        "required": [
          "membership_id",
          "name"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Project": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "all_access",
          "clients_enabled",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived",
              "trashed"
            ]
          },
          "all_access": {
            "type": "boolean"
          },
          "clients_enabled": {
            "type": "boolean"
          },
          "client_company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "share_new_items_with_clients": {
            "type": "boolean",
            "description": "The Clients tab's sharing default: new items start shared with the client when true"
          },
          "folder_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "ends_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "show_activity": {
            "type": "boolean"
          },
          "timesheet_enabled": {
            "type": "boolean",
            "description": "GET and PATCH /projects/{projectId} only, and never for clients: the project's Timesheet switch (off by default)"
          },
          "is_template": {
            "type": "boolean"
          },
          "is_sample": {
            "type": "boolean"
          },
          "starred": {
            "type": "boolean",
            "description": "Default listing only"
          },
          "joined": {
            "type": "boolean",
            "description": "?scope=all (owner) only"
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectMember"
            },
            "description": "?include=members only (first 5 active project members by name)"
          },
          "member_count": {
            "type": "integer",
            "description": "?include=members only; total active project members before the five-avatar cap"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ToolEntry": {
        "type": "object",
        "description": "One row of a project's tool list.",
        "required": [
          "tool",
          "label",
          "enabled",
          "position"
        ],
        "properties": {
          "tool": {
            "type": "string",
            "enum": [
              "message_board",
              "todos",
              "folder",
              "calendar",
              "board",
              "chat",
              "check_ins",
              "clients"
            ]
          },
          "label": {
            "type": "string",
            "description": "Display name after org-wide and per-project renames"
          },
          "enabled": {
            "type": "boolean"
          },
          "position": {
            "type": "number"
          },
          "container_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The tool's container recording; content is created under it"
          },
          "visible_to_clients": {
            "type": "boolean",
            "description": "The container's own client flag. Chat and Boards share as a whole (PUT /recordings/{container_id}/visibility cascades to every line, column and card); clients never receive an unshared Chat or board in their tool list and cannot open it"
          }
        }
      },
      "ProjectDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Project"
          },
          {
            "type": "object",
            "required": [
              "tools"
            ],
            "properties": {
              "tools": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ToolEntry"
                }
              },
              "dock": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ToolEntry"
                },
                "deprecated": true,
                "description": "Deprecated alias of tools (pre-2026-08-21 vocabulary); removed after the transition window"
              }
            }
          }
        ]
      },
      "OrganizationSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "href": {
            "type": "string",
            "format": "uri",
            "description": "The organization's API base URL"
          }
        }
      },
      "Authorization": {
        "type": "object",
        "description": "Credential introspection: make this your integration's first call.",
        "required": [
          "identity",
          "organizations",
          "scope",
          "expires_at"
        ],
        "properties": {
          "identity": {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Stable user id; names live on People endpoints"
              }
            }
          },
          "organizations": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/OrganizationSummary"
                },
                {
                  "type": "object",
                  "required": [
                    "membership_id",
                    "role",
                    "membership_kind"
                  ],
                  "properties": {
                    "membership_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Who the caller is inside this organization — the id assignee pickers and attribution speak"
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "owner",
                        "admin",
                        "member",
                        "client"
                      ]
                    },
                    "membership_kind": {
                      "type": "string",
                      "enum": [
                        "person",
                        "agent"
                      ],
                      "description": "\"agent\" when this credential belongs to an AI agent membership"
                    }
                  }
                }
              ]
            }
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "full"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "null for sessions and non-expiring tokens"
          }
        }
      },
      "PersonalAccessToken": {
        "type": "object",
        "required": [
          "id",
          "name",
          "token_prefix",
          "scope",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "token_prefix": {
            "type": "string",
            "description": "Enough of the secret to recognize it in a list; never the whole thing"
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "full"
            ]
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Minute precision"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NamedAgent": {
        "type": "object",
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "const": "agent"
          },
          "role": {
            "type": "string",
            "const": "member"
          },
          "listening": {
            "type": "boolean"
          },
          "removed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "operators": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "membership_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "membership_id",
                "name"
              ]
            }
          },
          "projects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            }
          },
          "connections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "last_used_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              },
              "required": [
                "id",
                "name",
                "created_at",
                "last_used_at"
              ]
            }
          },
          "attention": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "status": {
                  "type": "string"
                },
                "result": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "thread_path": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "status",
                "result",
                "thread_path"
              ]
            }
          },
          "can_connect": {
            "type": "boolean"
          }
        },
        "required": [
          "membership_id",
          "name",
          "image",
          "kind",
          "role",
          "listening",
          "removed_at",
          "operators",
          "projects",
          "connections",
          "attention",
          "can_connect"
        ]
      },
      "AgentInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "operator_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "maxItems": 100
          },
          "project_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 100
          }
        },
        "required": [
          "name",
          "operator_ids",
          "project_ids"
        ]
      },
      "AgentRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "completed",
              "failed",
              "interrupted",
              "dismissed"
            ]
          },
          "reason": {
            "type": "string"
          },
          "requester_membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string"
              },
              "project_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "parent_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "content": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "id",
              "type",
              "project_id",
              "parent_id",
              "title",
              "content",
              "updated_at"
            ]
          },
          "reply_to": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "comment",
                  "chat_message"
                ]
              },
              "recording_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              }
            },
            "required": [
              "kind",
              "recording_id"
            ]
          },
          "runtime_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "result": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status",
          "result"
        ]
      },
      "ConnectedApp": {
        "type": "object",
        "required": [
          "id",
          "client_name",
          "scope",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "client_name": {
            "type": "string"
          },
          "client_uri": {
            "type": [
              "string",
              "null"
            ]
          },
          "scope": {
            "type": "string",
            "description": "Space-separated OAuth scopes, e.g. `thicket.read thicket.write offline_access`."
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarRecurrence": {
        "type": [
          "object",
          "null"
        ],
        "description": "How a series repeats. `time_zone` anchors the series' wall clock across DST and fixes the calendar days `exdates`, `until`, and `occurrence_key` are read in.",
        "required": [
          "frequency"
        ],
        "properties": {
          "frequency": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "monthly",
              "yearly",
              "custom"
            ],
            "description": "UI presets \"every weekday\" and \"every other week\" map onto custom"
          },
          "interval": {
            "type": "integer",
            "minimum": 1,
            "maximum": 52,
            "description": "Every N periods"
          },
          "by_weekday": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "maxItems": 7,
            "description": "custom frequency: weekdays to repeat on, 0 = Sunday"
          },
          "monthly_mode": {
            "type": "string",
            "enum": [
              "day_of_month",
              "nth_weekday"
            ],
            "description": "monthly: follow the base date's day number, or its weekday instance (months missing the instance skip, RFC 5545 BYDAY)"
          },
          "exdates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            },
            "maxItems": 400,
            "description": "Anchor-zone day keys of occurrences that left the series: deleted (their events wait in the trash) or changed on their own. Accepted on create; on update the server keeps its own list"
          },
          "until": {
            "type": "string",
            "format": "date",
            "description": "Last day the series may occur on"
          },
          "time_zone": {
            "type": "string",
            "maxLength": 64,
            "description": "IANA zone anchoring the series' wall clock; default: the caller's zone"
          }
        }
      },
      "CalendarOccurrence": {
        "type": "object",
        "description": "One row of the global calendar: an event, one occurrence of a recurring event, or (with ?tasks=true) a due to-do or card.",
        "required": [
          "id",
          "kind",
          "type",
          "title",
          "start",
          "all_day",
          "circled",
          "mine"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The underlying recording; occurrences of the same series share it"
          },
          "kind": {
            "type": "string",
            "enum": [
              "event",
              "task"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "null: the account calendar"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "circled": {
            "type": "boolean"
          },
          "link": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "mine": {
            "type": "boolean",
            "description": "You created it or are invited"
          },
          "occurrence_key": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Recurring occurrences only: the anchor-zone day key the occurrences DELETE endpoint speaks. Never derive it from `start` client-side; the viewer's zone may disagree with the anchor's"
          },
          "visible_to_clients": {
            "type": "boolean",
            "description": "Shared with the project's client (the team's calendar marks it)"
          }
        }
      },
      "CalendarFeed": {
        "type": "object",
        "required": [
          "id",
          "url",
          "scope",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public /feeds/{token}.ics capability URL; the token is the credential"
          },
          "scope": {
            "type": "object",
            "description": "The filters the feed was minted with (stored camelCase)",
            "properties": {
              "projectId": {
                "type": "string",
                "format": "uuid"
              },
              "projectIds": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Selected project ids and/or \"account\""
              },
              "starredOnly": {
                "type": "boolean",
                "description": "The feed follows the member's starred projects"
              },
              "includeTasks": {
                "type": "boolean"
              },
              "just": {
                "type": "string",
                "enum": [
                  "me",
                  "everyone"
                ]
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarParseResult": {
        "type": "object",
        "description": "A prefill suggestion. When no date was found, `starts_at`, `ends_at`, and `all_day` are all null and `title` is the trimmed input.",
        "required": [
          "title",
          "starts_at",
          "ends_at",
          "all_day"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "The input with the date phrase removed"
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set when a range was given"
          },
          "all_day": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "true when no clock time was named"
          }
        }
      },
      "DmSummary": {
        "type": "object",
        "required": [
          "id",
          "title",
          "status",
          "cover_upload_id",
          "participants",
          "last_line",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The dm recording"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Group-chat name (PATCH /recordings/{id} renames); null: render the participant names"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ]
          },
          "cover_upload_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Group-chat cover image: an upload posted in the chat"
          },
          "participants": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "membership_id",
                "name"
              ],
              "properties": {
                "membership_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "last_line": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "preview",
              "created_at"
            ],
            "properties": {
              "preview": {
                "type": "string",
                "description": "Plain-text preview of the newest line"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SearchMetadata": {
        "type": "object",
        "required": [
          "types",
          "file_types",
          "default_type_label",
          "default_file_type_label"
        ],
        "properties": {
          "types": {
            "type": "array",
            "description": "Valid `type` filters; the null key is the unfiltered default",
            "items": {
              "type": "object",
              "required": [
                "key",
                "value"
              ],
              "properties": {
                "key": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "A recording type, or null for no filter"
                },
                "value": {
                  "type": "string",
                  "description": "Display label"
                }
              }
            }
          },
          "file_types": {
            "type": "array",
            "description": "Valid `file_type` filters; the null key is the unfiltered default",
            "items": {
              "type": "object",
              "required": [
                "key",
                "value"
              ],
              "properties": {
                "key": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "image",
                    "audio",
                    "video",
                    "pdf",
                    null
                  ]
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "default_type_label": {
            "type": "string"
          },
          "default_file_type_label": {
            "type": "string"
          }
        }
      },
      "ActivityEvent": {
        "type": "object",
        "required": [
          "id",
          "action",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string",
            "description": "What happened: created, completed, moved, and the rest of the event vocabulary. Calendar: `rescheduled` (a new date, time, all-day state, or repeat rule), `occurrence_removed` (one day of a repeating event, or every day from one on, went to the trash) and `occurrence_restored` (it came back)"
          },
          "detail": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "The action's particulars as stored, e.g. `{name}` on a project rename or the parent titles on a move; null when there are none"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "actor_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "recording_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain-text excerpt of the content involved"
          }
        }
      },
      "UpcomingReportEvent": {
        "type": "object",
        "description": "A dated row of the upcoming report; assignable rows extend it.",
        "required": [
          "id",
          "type",
          "title",
          "starts_at",
          "ends_at",
          "all_day",
          "project_id",
          "project_name",
          "recurring"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "recurring": {
            "type": "boolean"
          }
        }
      },
      "UpcomingReportAssignable": {
        "allOf": [
          {
            "$ref": "#/components/schemas/UpcomingReportEvent"
          },
          {
            "type": "object",
            "required": [
              "due_on",
              "assignees"
            ],
            "properties": {
              "due_on": {
                "type": "string",
                "format": "date"
              },
              "parent_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "parent_title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "assignees": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ReportAssignee"
                }
              }
            }
          }
        ]
      },
      "UpcomingReport": {
        "type": "object",
        "description": "`events`, `recurring_occurrences`, and `assignables` are the canonical keys. `schedule_entries` and `recurring_schedule_entry_occurrences` are deprecated aliases still served during the vocabulary transition; stop reading them.",
        "required": [
          "events",
          "recurring_occurrences",
          "assignables"
        ],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingReportEvent"
            },
            "description": "One-time events in the window"
          },
          "recurring_occurrences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingReportEvent"
            },
            "description": "Expanded occurrences of recurring events"
          },
          "assignables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingReportAssignable"
            },
            "description": "Dated open to-dos, cards, and card steps due in the window"
          },
          "schedule_entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingReportEvent"
            },
            "deprecated": true,
            "description": "Deprecated alias of events"
          },
          "recurring_schedule_entry_occurrences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpcomingReportEvent"
            },
            "deprecated": true,
            "description": "Deprecated alias of recurring_occurrences"
          }
        }
      },
      "HealthReportGauge": {
        "type": "object",
        "required": [
          "project_id",
          "project_name",
          "status",
          "position",
          "updated_at",
          "updated_by"
        ],
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "on_track",
              "some_risk",
              "concerned"
            ]
          },
          "position": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Needle position, 0 to 100"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the needle last moved"
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who last moved it"
          }
        }
      },
      "ReportAssignee": {
        "type": "object",
        "required": [
          "membership_id",
          "name"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ReportPerson": {
        "type": "object",
        "required": [
          "membership_id",
          "name",
          "email",
          "role"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "client"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AssignedReportItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "required": [
              "project_name",
              "assignees"
            ],
            "properties": {
              "project_name": {
                "type": "string"
              },
              "parent_title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "assignees": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ReportAssignee"
                }
              }
            }
          }
        ]
      },
      "AssignedReport": {
        "type": "object",
        "required": [
          "person",
          "grouped_by",
          "items"
        ],
        "properties": {
          "person": {
            "type": "object",
            "required": [
              "membership_id",
              "name",
              "role"
            ],
            "properties": {
              "membership_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "role": {
                "type": "string",
                "enum": [
                  "owner",
                  "admin",
                  "member",
                  "client"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "grouped_by": {
            "type": "string",
            "enum": [
              "bucket",
              "date"
            ],
            "description": "The sort the items arrived in"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AssignedReportItem"
            }
          }
        }
      },
      "OverdueReportItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AssignedReportItem"
          },
          {
            "type": "object",
            "required": [
              "days_late"
            ],
            "properties": {
              "days_late": {
                "type": "integer",
                "minimum": 1
              }
            }
          }
        ]
      },
      "OverdueReport": {
        "type": "object",
        "description": "Four lateness buckets; every overdue open to-do lands in exactly one.",
        "required": [
          "under_a_week_late",
          "over_a_week_late",
          "over_a_month_late",
          "over_three_months_late"
        ],
        "properties": {
          "under_a_week_late": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OverdueReportItem"
            }
          },
          "over_a_week_late": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OverdueReportItem"
            }
          },
          "over_a_month_late": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OverdueReportItem"
            }
          },
          "over_three_months_late": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OverdueReportItem"
            }
          }
        }
      },
      "PushTokenKind": {
        "type": "string",
        "enum": [
          "expo",
          "apns"
        ],
        "description": "The push token's transport: `expo` (the Expo push service, the 1.x app) or `apns` (Apple's service directly, the native iOS app). `fcm` joins with the Android app."
      },
      "MyNote": {
        "description": "Your private scratchpad: a `personal_note` recording with no project, no parent and no title, one per person, created empty on first read. `meta` is always null here; the restore point's body stays server-side.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "required": [
              "previous_version"
            ],
            "properties": {
              "previous_version": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The restore point: the pad as it stood when the current sitting began, moved forward by any save that discards most of the pad. Null until there is something to go back to.",
                "required": [
                  "saved_at"
                ],
                "properties": {
                  "saved_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the restorable version was last saved"
                  }
                }
              }
            }
          }
        ]
      },
      "MyNoteSave": {
        "type": "object",
        "properties": {
          "content": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50000,
            "description": "Plain text; converted to HTML"
          },
          "content_html": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100000,
            "description": "Rich HTML, sanitized; wins over content. Null, or an empty editor, clears the pad."
          },
          "base_updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "The `updated_at` this client last saw. When the pad has moved on since (another tab, the phone), the save answers 409 with the current pad under `error.note` instead of overwriting it."
          }
        }
      },
      "AssignmentRow": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "properties": {
              "project_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "parent_title": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "GroupedAssignment": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AssignmentRow"
          },
          {
            "type": "object",
            "required": [
              "children",
              "assigned_to_me"
            ],
            "properties": {
              "children": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AssignmentRow"
                },
                "description": "Card steps normalized under their parent card"
              },
              "assigned_to_me": {
                "type": "boolean",
                "description": "false only for a pulled-in parent card whose step is the actual assignment"
              },
              "priority_id": {
                "type": "string",
                "format": "uuid",
                "description": "Present on priorities rows only"
              }
            }
          }
        ]
      },
      "UpNextItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AssignmentRow"
          },
          {
            "type": "object",
            "required": [
              "priority_id"
            ],
            "properties": {
              "priority_id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "BookmarkRow": {
        "type": "object",
        "required": [
          "recording_id",
          "type",
          "created_at"
        ],
        "properties": {
          "recording_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MyCheerRow": {
        "type": "object",
        "required": [
          "id",
          "content",
          "created_at",
          "cheerer_membership_id",
          "recording_id",
          "recording_type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string",
            "description": "The reaction: up to 16 chars of emoji or text"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "cheerer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "cheerer_membership_id": {
            "type": "string",
            "format": "uuid",
            "description": "Who cheered, by membership id"
          },
          "recording_id": {
            "type": "string",
            "format": "uuid"
          },
          "recording_type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "recording_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "DoTodayItem": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "start",
          "all_day"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "event",
              "task"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MyEventRow": {
        "type": "object",
        "required": [
          "id",
          "start",
          "all_day",
          "circled"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "null: an account-calendar event"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "link": {
            "type": [
              "string",
              "null"
            ],
            "description": "The event's join link, when set"
          },
          "circled": {
            "type": "boolean"
          }
        }
      },
      "NotificationRow": {
        "type": "object",
        "required": [
          "id",
          "action",
          "title",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string",
            "description": "What happened: assigned, mentioned, commented, chatted, chat_activity, announced, reminder, …"
          },
          "title": {
            "type": "string",
            "description": "Usually the actor's name; \"Thicket\" for system rows"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reminder phrases are re-derived at read time so a day-before reminder never says Tomorrow on the day itself"
          },
          "bundle_count": {
            "type": "integer",
            "description": "Chat rows: lines folded into this one entry while it stayed unread"
          },
          "recording_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "bulletin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Platform announcements (action announced): open GET /api/v1/{orgSlug}/bulletins/{bulletin_id}"
          },
          "timesheet_week_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A rejected timesheet week (action timesheet_rejected, the reason in body): the week the row is about, instead of a recording"
          },
          "timesheet_week_start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "That week's first day: open GET /api/v1/{orgSlug}/my/timesheet?week={timesheet_week_start}"
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "null for actor-less rows (announcements, reminders)"
          },
          "recording_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The notified-about recording's type"
          },
          "recording_parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "actor_membership_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Who acted, by the id mentions, assignees, and creators speak; null for actor-less rows"
          },
          "actor_role": {
            "type": [
              "string",
              "null"
            ],
            "description": "owner, admin, member, or client; agent runtimes exclude client authors fail-closed"
          },
          "actor_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "person or agent"
          },
          "from_operator": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Agent recipients only: whether the actor is one of the agent's operators. Null for people."
          },
          "directive": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Agent recipients only: true when the row addresses the agent (mentioned or assigned) from someone allowed to direct it under its policy. Null for people."
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SnoozeRow": {
        "type": "object",
        "required": [
          "recording_id",
          "type",
          "resurface_at"
        ],
        "properties": {
          "recording_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "resurface_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SecurityState": {
        "type": "object",
        "required": [
          "two_factor_enabled",
          "has_password",
          "sign_in_label"
        ],
        "properties": {
          "two_factor_enabled": {
            "type": "boolean"
          },
          "has_password": {
            "type": "boolean"
          },
          "sign_in_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Google, Apple, Google or Apple, or null"
          }
        }
      },
      "Preferences": {
        "type": "object",
        "description": "The user's personal preferences, one document across organizations. Field families: locale and display (timezone: a valid IANA zone, time_format 12h|24h, week_start sunday…saturday, theme light|dark|system, background_tint); notification routing (notify_scope everything|mentions, though direct actions like mentions, assignments, pings, and approvals always come through; email_notifications gates email copies; notifications_silenced is the in-app \"Shhh\" mute with the badge hidden; desktop_notifications; unread_badge for the \"(N)\" tab title); quiet hours (work_hours_enabled plus work_hours as per-day {start, end} HH:MM windows or null; catch_up_summary opts into one end-of-day summary of held email); email channels (notify_event_reminders, notify_assignment_reminders for due-soon reminders, weekly_assignments_email for the Monday summary, announcement_emails, onboarding_emails, notify_cheers, activity_digest); profile extras (out_of_office_start/out_of_office_end ISO dates, location, current_status); view choices (todos_view list|cards, docs_folders_open). GET returns every field; PATCH accepts any subset."
      },
      "OrgRole": {
        "type": "string",
        "description": "Owner and admin administer the org; members are the team; clients see only client-visible recordings in clients-enabled projects they were added to.",
        "enum": [
          "owner",
          "admin",
          "member",
          "client"
        ]
      },
      "OrgListItem": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "role",
          "sole_owner",
          "requires_two_factor_setup"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "logo": {
            "type": "string",
            "description": "Omitted when unset"
          },
          "sole_owner": {
            "type": "boolean",
            "description": "The caller is this org's only active owner"
          },
          "requires_two_factor_setup": {
            "type": "boolean",
            "description": "This non-client caller must enroll in 2FA before accessing org-scoped routes"
          }
        }
      },
      "CancelledOrg": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "role",
          "cancelled_at",
          "purge_after",
          "can_restore"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "purge_after": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the 30-day restore window closes"
          },
          "can_restore": {
            "type": "boolean",
            "description": "True where the caller is an owner"
          }
        }
      },
      "Person": {
        "type": "object",
        "required": [
          "membership_id",
          "name",
          "email",
          "role",
          "created_at"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "Same-origin avatar path or external SSO URL; null means render an initials disc"
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "kind": {
            "type": "string",
            "enum": [
              "person",
              "agent"
            ],
            "description": "Historical agent identities remain distinguishable for attribution. New integrations use an existing person's OAuth connection."
          },
          "active": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Agents only: true while the agent's runtime is listening. Null for people."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "out_of_office": {
            "type": [
              "object",
              "null"
            ],
            "description": "null while off",
            "properties": {
              "start_date": {
                "type": "string",
                "format": "date"
              },
              "end_date": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "email_bouncing": {
            "type": "boolean",
            "description": "True while the email provider has stopped mailing this address after a hard bounce or a spam complaint. Thicket's notifications to this person are not being delivered until they change their address in My settings."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Invitation": {
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "declined",
          "created_at",
          "email_status",
          "join_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "company_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "declined": {
            "type": "boolean",
            "description": "Declined invites stay listed; a resend revives them"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "email_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "skipped",
              "sent",
              "delivery_delayed",
              "delivered",
              "failed",
              "bounced",
              "complained",
              null
            ],
            "description": "The delivery ledger's word on the latest invite email"
          },
          "join_url": {
            "type": "string",
            "format": "uri",
            "description": "The same link the email button carries, for sharing over another channel"
          }
        }
      },
      "InviteLink": {
        "type": "object",
        "required": [
          "enabled",
          "url"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "null while disabled"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Present once the link has been minted"
          }
        }
      },
      "AccessProject": {
        "type": "object",
        "description": "One row of a change-access listing.",
        "required": [
          "id",
          "name",
          "status",
          "all_access",
          "clients_enabled"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "archived"
            ]
          },
          "all_access": {
            "type": "boolean"
          },
          "clients_enabled": {
            "type": "boolean"
          },
          "has_row": {
            "type": "boolean",
            "description": "Membership listings: an explicit grant exists (non-clients reach all_access projects implicitly)"
          },
          "granted": {
            "type": "boolean",
            "description": "Invitation listings: the invitation grants this project on accept"
          }
        }
      },
      "PersonProjectAccess": {
        "type": "object",
        "required": [
          "membership_id",
          "role",
          "projects"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AccessProject"
            },
            "description": "Rows carry has_row"
          }
        }
      },
      "InvitationProjectAccess": {
        "type": "object",
        "required": [
          "invitation_id",
          "email",
          "role",
          "declined",
          "projects"
        ],
        "properties": {
          "invitation_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "$ref": "#/components/schemas/OrgRole"
          },
          "declined": {
            "type": "boolean"
          },
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AccessProject"
            },
            "description": "Rows carry granted"
          }
        }
      },
      "OutOfOffice": {
        "type": "object",
        "required": [
          "person",
          "enabled",
          "ongoing",
          "start_date",
          "end_date"
        ],
        "properties": {
          "person": {
            "type": "object",
            "required": [
              "membership_id",
              "name"
            ],
            "properties": {
              "membership_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "enabled": {
            "type": "boolean"
          },
          "ongoing": {
            "type": "boolean",
            "description": "Enabled and today falls inside the range"
          },
          "start_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "end_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "PersonEvent": {
        "type": "object",
        "description": "One entry of a person's activity trail.",
        "required": [
          "id",
          "action",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string"
          },
          "detail": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "recording_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain-text content excerpt"
          }
        }
      },
      "Company": {
        "type": "object",
        "required": [
          "id",
          "name",
          "is_client",
          "dms_disabled",
          "member_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "is_client": {
            "type": "boolean"
          },
          "dms_disabled": {
            "type": "boolean",
            "description": "Members drop from chat pickers, can't start DMs, and existing conversations freeze read-only"
          },
          "member_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PeopleGroup": {
        "type": "object",
        "required": [
          "id",
          "name",
          "members"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "members": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "membership_id",
                "name"
              ],
              "properties": {
                "membership_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "PersonMerge": {
        "type": "object",
        "required": [
          "id",
          "winner_name",
          "loser_name",
          "loser_email",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "winner_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "loser_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "loser_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OrgSettings": {
        "type": "object",
        "required": [
          "require_two_factor",
          "history_retention_days",
          "tool_names",
          "voice_notes_enabled",
          "restrict_project_edits",
          "restrict_people_edits",
          "restrict_content_actions",
          "restrict_public_links",
          "limit_comment_editing"
        ],
        "properties": {
          "require_two_factor": {
            "type": "boolean",
            "description": "Non-client members without TOTP are funneled into 2FA setup at org entry; clients exempt"
          },
          "history_retention_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null keeps chat and DM history forever; a number hard-deletes older lines lazily on read, permanently"
          },
          "tool_names": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-tool display-name overrides, keyed by tool (message_board, todos, folder, calendar, board, chat, check_ins, clients)"
          },
          "voice_notes_enabled": {
            "type": "boolean"
          },
          "restrict_project_edits": {
            "type": "boolean"
          },
          "restrict_people_edits": {
            "type": "boolean"
          },
          "restrict_content_actions": {
            "type": "boolean"
          },
          "restrict_public_links": {
            "type": "boolean"
          },
          "limit_comment_editing": {
            "type": "boolean"
          },
          "timesheet_week_start": {
            "type": "string",
            "enum": [
              "sunday",
              "monday",
              "tuesday",
              "wednesday",
              "thursday",
              "friday",
              "saturday"
            ],
            "description": "The day the weekly timesheet starts (Admin › Timesheets; default monday). Absent for clients"
          },
          "timesheet_approvals_enabled": {
            "type": "boolean",
            "description": "Admin › Timesheets: people submit each week for approval, and a change made after that is flagged, never blocked (default false). Absent for clients"
          }
        }
      },
      "OrgAccount": {
        "type": "object",
        "description": "The account document: identity, owners, plan limits, can-do flags, and settings.",
        "required": [
          "id",
          "name",
          "slug",
          "created_at",
          "updated_at",
          "owner_names",
          "plan",
          "limits",
          "settings"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "logo": {
            "type": "string",
            "description": "Omitted when unset"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "owner_names": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "plan": {
            "type": "object",
            "required": [
              "tier",
              "status",
              "trial_ends_at",
              "project_limit",
              "people_limit",
              "storage_limit_bytes"
            ],
            "properties": {
              "tier": {
                "type": "string",
                "enum": [
                  "free",
                  "starter",
                  "pro",
                  "comped"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "active",
                  "trialing",
                  "past_due",
                  "cancelled",
                  "pending",
                  "comped",
                  "lapsed"
                ]
              },
              "trial_ends_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "project_limit": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "null means unlimited"
              },
              "people_limit": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "One pool for members and guests alike, clients included; null means unlimited"
              },
              "storage_limit_bytes": {
                "type": "integer"
              }
            }
          },
          "limits": {
            "type": "object",
            "required": [
              "can_create_projects",
              "can_create_users",
              "can_upload_files"
            ],
            "properties": {
              "can_create_projects": {
                "type": "boolean"
              },
              "can_create_users": {
                "type": "boolean"
              },
              "can_upload_files": {
                "type": "boolean"
              }
            }
          },
          "settings": {
            "type": "object",
            "required": [
              "require_two_factor",
              "history_retention_days",
              "tool_names",
              "voice_notes_enabled"
            ],
            "properties": {
              "require_two_factor": {
                "type": "boolean"
              },
              "history_retention_days": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "tool_names": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "voice_notes_enabled": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "StorageUsage": {
        "type": "object",
        "required": [
          "total_bytes",
          "file_count",
          "limit_bytes",
          "largest"
        ],
        "properties": {
          "total_bytes": {
            "type": "integer"
          },
          "file_count": {
            "type": "integer"
          },
          "limit_bytes": {
            "type": "integer"
          },
          "largest": {
            "type": "array",
            "description": "The largest files; private-chat and unposted-draft uploads count toward usage but are not listed",
            "items": {
              "type": "object",
              "required": [
                "recording_id",
                "filename",
                "byte_size",
                "content_type",
                "created_at"
              ],
              "properties": {
                "recording_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "filename": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "byte_size": {
                  "type": "integer"
                },
                "content_type": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "project_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "status": {
                  "$ref": "#/components/schemas/RecordingStatus"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "BillingInfo": {
        "type": "object",
        "required": [
          "tier",
          "status",
          "interval",
          "trial_ends_at",
          "current_period_ends_at",
          "cancel_at_period_end",
          "has_stripe_customer",
          "usage",
          "limits"
        ],
        "properties": {
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "starter",
              "pro",
              "comped"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "trialing",
              "past_due",
              "cancelled",
              "pending",
              "comped",
              "lapsed"
            ]
          },
          "interval": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "monthly",
              "yearly",
              null
            ]
          },
          "trial_ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "current_period_ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_at_period_end": {
            "type": "boolean"
          },
          "has_stripe_customer": {
            "type": "boolean"
          },
          "usage": {
            "type": "object",
            "required": [
              "projects",
              "members",
              "guests",
              "invited_members",
              "invited_guests",
              "storage_bytes"
            ],
            "properties": {
              "projects": {
                "type": "integer"
              },
              "members": {
                "type": "integer"
              },
              "guests": {
                "type": "integer"
              },
              "invited_members": {
                "type": "integer"
              },
              "invited_guests": {
                "type": "integer"
              },
              "storage_bytes": {
                "type": "integer"
              }
            }
          },
          "limits": {
            "type": "object",
            "description": "-1 means unlimited; one people pool covers members and guests alike, clients included",
            "required": [
              "projects",
              "people",
              "storage_bytes",
              "activity_history_days"
            ],
            "properties": {
              "projects": {
                "type": "integer"
              },
              "people": {
                "type": "integer"
              },
              "storage_bytes": {
                "type": "integer"
              },
              "activity_history_days": {
                "type": "integer"
              }
            }
          }
        }
      },
      "BulletinSummary": {
        "type": "object",
        "required": [
          "id",
          "title",
          "excerpt",
          "published_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "description": "Personalized for the caller"
          },
          "excerpt": {
            "type": "string",
            "description": "Capped at 200 chars"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Bulletin": {
        "type": "object",
        "required": [
          "id",
          "status",
          "title",
          "body_html",
          "author_name",
          "published_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published"
            ],
            "description": "A draft only ever reaches a test recipient; render it behind a test-preview note"
          },
          "title": {
            "type": "string",
            "description": "Personalized for the caller"
          },
          "body_html": {
            "type": "string",
            "description": "Sanitized HTML, personalized for the caller"
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "author_avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "MessageCategory": {
        "type": "object",
        "required": [
          "id",
          "name",
          "icon"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "description": "An emoji"
          }
        }
      },
      "TrashedItem": {
        "type": "object",
        "required": [
          "id",
          "type",
          "title",
          "project_id",
          "project_name",
          "trashed_at",
          "trashed_by_name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Comments list as Re: {thread title}"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "trashed_at": {
            "type": "string",
            "format": "date-time"
          },
          "trashed_by_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Calendar events: when the trashed event starts. A deleted day of a repeating event is dated by that day, a deleted \"all future\" part by its first day"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "recurring": {
            "type": "boolean",
            "description": "A repeating event, or a deleted \"all future\" part of one"
          }
        }
      },
      "ReassignImpact": {
        "type": "object",
        "required": [
          "count",
          "projects"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "description": "Open to-dos affected"
          },
          "projects": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "count"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "ShareEverythingCounts": {
        "type": "object",
        "required": [
          "messages",
          "todolists",
          "docs_and_files",
          "events",
          "questions",
          "updates",
          "tools",
          "within"
        ],
        "properties": {
          "messages": {
            "type": "integer"
          },
          "todolists": {
            "type": "integer"
          },
          "docs_and_files": {
            "type": "integer",
            "description": "Docs, files, links and folders at the top of Docs & Files"
          },
          "events": {
            "type": "integer"
          },
          "questions": {
            "type": "integer",
            "description": "Check-in questions"
          },
          "updates": {
            "type": "integer",
            "description": "Progress and health updates"
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "chat",
                "board"
              ]
            },
            "description": "The whole-tool shares"
          },
          "within": {
            "type": "integer",
            "description": "Everything that comes along inside the above"
          }
        }
      },
      "ProjectLink": {
        "type": "object",
        "description": "An external link shown with the project's tools.",
        "required": [
          "id",
          "url",
          "title",
          "service",
          "image_url",
          "icon_url",
          "visible_to_clients",
          "position",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string",
            "maxLength": 26
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "service": {
            "type": "string",
            "description": "The chooser tile it was added from: an id from the web's external link services (`github`, `figma`, `zoom`, …), `other` being the External Service form"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "An uploaded image's same-origin path (a 160×160 webp, immutable-cacheable, no session needed), or null when the card shows the service's brand tile"
          },
          "icon_url": {
            "type": "string",
            "description": "Whichever image the card renders: `image_url` when one is uploaded, else the service's brand tile (`/app-icons/{service}.svg`)"
          },
          "visible_to_clients": {
            "type": "boolean"
          },
          "position": {
            "type": "number"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProjectEvent": {
        "type": "object",
        "description": "One row of the project timeline.",
        "required": [
          "id",
          "action",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string",
            "description": "What happened: created, completed, archived, …"
          },
          "detail": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "recording_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "recording_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "A RecordingType value, or null for project-level events"
          },
          "actor_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The actor's membership id"
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain text pulled from the recording's content"
          }
        }
      },
      "ProjectAudience": {
        "type": "object",
        "description": "Who an item in a project reaches (`GET /projects/{projectId}/audience`).",
        "required": [
          "people",
          "groups"
        ],
        "properties": {
          "people": {
            "type": "array",
            "description": "Everyone the item reaches, by name.",
            "items": {
              "type": "object",
              "required": [
                "membership_id",
                "name",
                "image",
                "role"
              ],
              "properties": {
                "membership_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "image": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Same-origin path or external URL; null means render an initials disc"
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "owner",
                    "admin",
                    "member",
                    "client"
                  ]
                }
              }
            }
          },
          "groups": {
            "type": "array",
            "description": "The account's groups with at least one member the item reaches, each narrowed to those members. Picking a group assigns, invites or mentions exactly `member_ids`. Always empty for a client caller.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "member_ids"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "member_ids": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        }
      },
      "ProjectPerson": {
        "type": "object",
        "description": "One person with access to the project.",
        "required": [
          "membership_id",
          "name",
          "role"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "Same-origin path or external URL; null means render an initials disc"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member",
              "client"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "HealthUpdate": {
        "type": "object",
        "description": "One saved move of the health needle, newest first in the gauge's updates.",
        "required": [
          "id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "A health_update recording: commentable, cheerable, trashable"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "on_track",
              "some_risk",
              "concerned",
              null
            ]
          },
          "position": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "previous_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "on_track",
              "some_risk",
              "concerned",
              null
            ]
          },
          "previous_position": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sanitized HTML"
          },
          "visible_to_clients": {
            "type": "boolean"
          },
          "comments_count": {
            "type": "integer"
          },
          "creator_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "HealthGauge": {
        "type": "object",
        "description": "The project health gauge and its update history.",
        "required": [
          "enabled",
          "status",
          "position",
          "updated_at",
          "updates"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "on_track",
              "some_risk",
              "concerned",
              null
            ],
            "description": "null until the first update"
          },
          "position": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HealthUpdate"
            }
          }
        }
      },
      "ProjectTrashItem": {
        "type": "object",
        "description": "One trashed recording on the project trash page.",
        "required": [
          "id",
          "type",
          "trashed_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "trashed_at": {
            "type": "string",
            "format": "date-time"
          },
          "trashed_by_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Calendar events: when the trashed event starts. A deleted day of a repeating event is dated by that day, a deleted \"all future\" part by its first day"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "all_day": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "recurring": {
            "type": "boolean",
            "description": "A repeating event, or a deleted \"all future\" part of one"
          }
        }
      },
      "RoadmapMarker": {
        "type": "object",
        "description": "An org-wide roadmap marker (bc3 lineup_markers conventions).",
        "required": [
          "id",
          "name",
          "date",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClientVisibility": {
        "type": "object",
        "required": [
          "recording_id",
          "kind",
          "visible",
          "from"
        ],
        "properties": {
          "recording_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "own",
              "inherited",
              "held",
              "always",
              "never",
              "tool"
            ]
          },
          "visible": {
            "type": "boolean",
            "description": "Whether the project's client can open it"
          },
          "from": {
            "type": [
              "object",
              "null"
            ],
            "description": "The anchor an inherited or held-back recording follows; null otherwise",
            "required": [
              "id",
              "type",
              "title"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string"
              },
              "title": {
                "type": "string"
              }
            }
          }
        }
      },
      "RecordingRecurrence": {
        "type": [
          "object",
          "null"
        ],
        "description": "The repeat schedule for a recurring to-do series (stored as meta.recurrence, the calendar's Recurrence shape). Anchored on starts_on when the to-do spans days, else due_on.",
        "required": [
          "frequency"
        ],
        "properties": {
          "frequency": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "monthly",
              "yearly",
              "custom"
            ]
          },
          "interval": {
            "type": "integer",
            "minimum": 1,
            "maximum": 52
          },
          "by_weekday": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "maxItems": 7
          },
          "monthly_mode": {
            "type": "string",
            "enum": [
              "day_of_month",
              "nth_weekday"
            ]
          },
          "until": {
            "type": "string",
            "format": "date",
            "description": "Caps the last occurrence"
          }
        }
      },
      "QuestionSchedule": {
        "type": "object",
        "description": "Check-in questions only: when the question is asked.",
        "required": [
          "frequency",
          "hour"
        ],
        "properties": {
          "frequency": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "every_other_week",
              "monthly"
            ]
          },
          "days": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "minItems": 1,
            "description": "daily: which weekdays"
          },
          "day_of_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "weekly / every_other_week"
          },
          "day_of_month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28,
            "description": "monthly"
          },
          "hour": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23
          },
          "minute": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59
          }
        }
      },
      "RecordingContentAttachment": {
        "type": "object",
        "description": "Structured metadata for one file embedded in a recording's rich text.",
        "required": [
          "id",
          "filename",
          "content_type",
          "byte_size",
          "previewable",
          "url",
          "download_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "byte_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "previewable": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "download_url": {
            "type": "string"
          }
        }
      },
      "RecordingDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "required": [
              "content_attachments"
            ],
            "properties": {
              "creator_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Author byline; survives the person leaving the org"
              },
              "creator_image": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "assignee_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Membership ids currently assigned (to-dos, cards, events); [] otherwise"
              },
              "mentioned_membership_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Every membership @-mentioned in the content, by id: the corroboration fact an agent runtime checks"
              },
              "content_attachments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RecordingContentAttachment"
                },
                "description": "Files embedded in the rich text; always present, [] when none"
              }
            }
          }
        ]
      },
      "ChildListRow": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "properties": {
              "creator_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "creator_image": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comment_count": {
                "type": "integer"
              },
              "last_comment_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "cheers": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InlineCheer"
                },
                "description": "chat_message rows only"
              },
              "assignees": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "membership_id",
                    "name"
                  ],
                  "properties": {
                    "membership_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "image": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Same-origin avatar path, or null"
                    }
                  }
                },
                "description": "client_approval and card rows only"
              },
              "step_count": {
                "type": "integer",
                "description": "card rows only: active steps under the card"
              },
              "steps_done": {
                "type": "integer",
                "description": "card rows only: completed steps"
              },
              "card_count": {
                "type": "integer",
                "description": "column rows only: active cards in the column (held cards included)"
              },
              "todo_open_count": {
                "type": "integer",
                "description": "todolist rows only: open to-dos in the list, its groups included"
              },
              "todo_done_count": {
                "type": "integer",
                "description": "todolist rows only: completed to-dos, groups included"
              },
              "byte_size": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "upload rows only: the current version's size"
              },
              "content_type": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "upload rows only: the current version's MIME type"
              }
            }
          }
        ]
      },
      "CommentRow": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Recording"
          },
          {
            "type": "object",
            "properties": {
              "creator_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "creator_image": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "InlineCheer": {
        "type": "object",
        "description": "A cheer as embedded on chat rows and change-log events.",
        "required": [
          "id",
          "content",
          "membership_id",
          "person"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string"
          },
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "person": {
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "Chat rows only"
          }
        }
      },
      "RecordingCheerRow": {
        "type": "object",
        "description": "One row of the standalone cheers listing.",
        "required": [
          "id",
          "content",
          "membership_id",
          "name",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string"
          },
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RecordingEvent": {
        "type": "object",
        "description": "One change-log row: what happened, who did it, and its cheers.",
        "required": [
          "id",
          "action",
          "created_at",
          "cheers"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "type": "string",
            "description": "created, completed, moved, version_added, visibility/hold/comment toggles, ..."
          },
          "detail": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "moved rows carry fromParentTitle/toParentTitle captured at move time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "actor_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "cheers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InlineCheer"
            }
          }
        }
      },
      "RecordingReference": {
        "type": "object",
        "description": "A recording whose content links to this one (the References tab).",
        "required": [
          "id",
          "type",
          "referenced_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/RecordingType"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "creator_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "referenced_at": {
            "type": "string",
            "format": "date-time"
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RecordingPersonRow": {
        "type": "object",
        "description": "One person on an assignee or subscriber list.",
        "required": [
          "membership_id",
          "name"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RecordingBatchResult": {
        "type": "object",
        "description": "What a bulk verb touched.",
        "required": [
          "count",
          "ids"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "description": "Rows the action actually changed"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The affected ids; for copy, the copies' ids"
          },
          "group_id": {
            "type": "string",
            "format": "uuid",
            "description": "group action only: the created group"
          }
        }
      },
      "CheckInSchedule": {
        "type": "object",
        "description": "When a check-in question asks. Daily uses `days`; weekly and every_other_week use `day_of_week`; monthly means the first `day_of_week` of the month unless `day_of_month` is set.",
        "required": [
          "frequency",
          "hour"
        ],
        "properties": {
          "frequency": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "every_other_week",
              "monthly"
            ]
          },
          "days": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "minItems": 1,
            "description": "Daily only: which weekdays to ask (0=Sunday to 6=Saturday; default Monday to Friday, weekends allowed)"
          },
          "day_of_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "Weekly, every_other_week, and default monthly shape; the every-other-week alternation anchors at creation"
          },
          "day_of_month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28,
            "description": "Monthly alternative to the first-weekday shape"
          },
          "hour": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23
          },
          "minute": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59
          }
        }
      },
      "CheckInAskee": {
        "type": "object",
        "required": [
          "membership_id",
          "name"
        ],
        "properties": {
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ProgressDot": {
        "type": "object",
        "description": "One tracked to-do list on the progress chart.",
        "required": [
          "id",
          "title",
          "position",
          "color"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The todolist's id"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "position": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 = working it out, 1 = getting it done"
          },
          "color": {
            "type": "string",
            "description": "Assigned at first track, stable across re-tracks"
          }
        }
      },
      "ProgressSnapshotDot": {
        "type": "object",
        "description": "A dot as captured in a saved update's snapshot.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": "string"
          },
          "position": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        }
      },
      "PublicLinkState": {
        "type": "object",
        "required": [
          "published",
          "url"
        ],
        "properties": {
          "published": {
            "type": "boolean"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The /public/{token} capability URL: anyone holding it can read the sanitized content"
          }
        }
      },
      "ProjectFolder": {
        "type": "object",
        "description": "An account-wide home-screen grouping of projects.",
        "required": [
          "id",
          "name",
          "color",
          "position",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "position": {
            "type": "number"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UploadVersion": {
        "type": "object",
        "description": "A kept past version of a file.",
        "required": [
          "id",
          "filename",
          "byte_size",
          "content_type",
          "created_at",
          "download_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "filename": {
            "type": "string"
          },
          "byte_size": {
            "type": "integer"
          },
          "content_type": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "download_url": {
            "type": "string",
            "description": "Relative API path of this version's download redirect"
          }
        }
      },
      "EventCheer": {
        "type": "object",
        "required": [
          "id",
          "content",
          "membership_id",
          "person"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "type": "string",
            "description": "Up to 16 characters of text or emoji"
          },
          "membership_id": {
            "type": "string",
            "format": "uuid"
          },
          "person": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TimesheetEntry": {
        "type": "object",
        "required": [
          "id",
          "type",
          "date",
          "hours",
          "description",
          "person",
          "creator",
          "project",
          "parent",
          "occurrence_date",
          "absence_type",
          "created_at",
          "updated_at",
          "regular_hours",
          "overtime_hours"
        ],
        "description": "Hours on a project, on one of its items, or as absence: a team-only recording. It counts while its item exists, goes to the trash and comes back with it, and moves with it. Clients never see one, and no generic `/recordings` route returns one.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "timesheet_entry"
            ]
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "hours": {
            "type": "string",
            "description": "Decimal hours as a string: \"2.0\", \"1.5\", \"0.12\""
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "person": {
            "$ref": "#/components/schemas/ProjectMember"
          },
          "creator": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "membership_id",
              "name"
            ],
            "properties": {
              "membership_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "image": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "Who logged it"
          },
          "project": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "description": "Null for absence"
          },
          "parent": {
            "$ref": "#/components/schemas/TimesheetEntryParent"
          },
          "occurrence_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The day of a repeating event; null otherwise"
          },
          "absence_type": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "regular_hours": {
            "type": "string",
            "description": "Regular portion of hours, as a decimal string. hours is regular_hours plus overtime_hours."
          },
          "overtime_hours": {
            "type": "string",
            "description": "Overtime portion of hours, as a decimal string. Existing entries default to \"0.0\"."
          }
        }
      },
      "TimesheetEntryParent": {
        "type": [
          "object",
          "null"
        ],
        "required": [
          "id",
          "type",
          "title",
          "app_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "For a `timesheet` parent, the project's timesheet: `POST /recordings/{id}/timesheet/entries` there logs time on the project itself"
          },
          "type": {
            "type": "string",
            "enum": [
              "timesheet",
              "todo",
              "message",
              "document",
              "upload",
              "card",
              "calendar_event"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"Timesheet\" for time on the project itself"
          },
          "app_url": {
            "type": "string",
            "format": "uri",
            "description": "The item's page (a repeating event's carries `?occurrence=`), or the project's timesheet page"
          }
        },
        "description": "What the time is on: the item, or the project's timesheet (`type: \"timesheet\"`) for time on the project itself; null for absence."
      },
      "TimesheetWeekRow": {
        "type": "object",
        "required": [
          "recording_id",
          "occurrence_date",
          "project",
          "item",
          "added",
          "can_log",
          "can_edit",
          "closed_reason"
        ],
        "description": "A row of the weekly timesheet. An entry is in the row whose `recording_id` and `occurrence_date` are its `parent.id` and `occurrence_date`; new time in the row goes to `POST /recordings/{recording_id}/timesheet/entries` with `occurrence` set to `occurrence_date`.",
        "properties": {
          "recording_id": {
            "type": "string",
            "format": "uuid",
            "description": "What its time hangs under: the item, the recording holding a repeating event's day, or the project's timesheet (time on the project itself)"
          },
          "occurrence_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "An event's day; null otherwise"
          },
          "project": {
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "item": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "type",
              "title",
              "app_url"
            ],
            "description": "Null for time on the project itself",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string",
                "enum": [
                  "todo",
                  "message",
                  "document",
                  "upload",
                  "card",
                  "calendar_event"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "app_url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "added": {
            "type": "boolean",
            "description": "Added to the week (it stays without hours); false when only its hours put it there"
          },
          "can_log": {
            "type": "boolean",
            "description": "New time can go in it"
          },
          "can_edit": {
            "type": "boolean",
            "description": "Its entries can change"
          },
          "closed_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it takes no new time, when it doesn't"
          }
        }
      },
      "TimesheetWeek": {
        "type": "object",
        "required": [
          "person",
          "week_start",
          "week_end",
          "week_starts_on",
          "days",
          "writable",
          "approvals_enabled",
          "status",
          "approval",
          "total_hours",
          "rows",
          "absence_types",
          "projects",
          "entries"
        ],
        "properties": {
          "person": {
            "$ref": "#/components/schemas/ProjectMember"
          },
          "week_start": {
            "type": "string",
            "format": "date"
          },
          "week_end": {
            "type": "string",
            "format": "date"
          },
          "week_starts_on": {
            "type": "string",
            "enum": [
              "sunday",
              "monday",
              "tuesday",
              "wednesday",
              "thursday",
              "friday",
              "saturday"
            ],
            "description": "The org's timesheet week start (Admin › Timesheets)"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            },
            "minItems": 7,
            "maxItems": 7
          },
          "writable": {
            "type": "boolean",
            "description": "The caller can change this week: their own, or an owner's or admin's view of someone still on the team"
          },
          "approvals_enabled": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "not_submitted",
              "submitted",
              "approved",
              "changed",
              "rejected",
              null
            ],
            "description": "The week's approval status; null while approvals are off"
          },
          "approval": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "person",
              "week_start",
              "week_end",
              "status",
              "changed_since",
              "submitted_at",
              "submitted_by",
              "decided_at",
              "decided_by",
              "self_approved",
              "rejection_reason"
            ],
            "properties": {
              "person": {
                "$ref": "#/components/schemas/ProjectMember"
              },
              "week_start": {
                "type": "string",
                "format": "date"
              },
              "week_end": {
                "type": "string",
                "format": "date"
              },
              "status": {
                "type": "string",
                "enum": [
                  "not_submitted",
                  "submitted",
                  "approved",
                  "changed",
                  "rejected"
                ],
                "description": "As read now: `changed` once the week's hours no longer match what was submitted or approved (an edit, a new or deleted entry, a trashed or restored item, a move; never a notes-only edit), until it's resubmitted or approved again. A rejected week stays `rejected` until it's resubmitted"
              },
              "changed_since": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "submission",
                  "approval",
                  null
                ],
                "description": "A changed week: whether it changed since it was submitted or since it was approved"
              },
              "submitted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "submitted_by": {
                "type": [
                  "object",
                  "null"
                ],
                "required": [
                  "membership_id",
                  "name"
                ],
                "properties": {
                  "membership_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "image": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Who submitted it: the person, or an owner or admin for them"
              },
              "decided_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When it was approved or rejected; null while it waits on a decision"
              },
              "decided_by": {
                "type": [
                  "object",
                  "null"
                ],
                "required": [
                  "membership_id",
                  "name"
                ],
                "properties": {
                  "membership_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "image": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "description": "Who approved or rejected it"
              },
              "self_approved": {
                "type": "boolean",
                "description": "An owner or admin approved their own week"
              },
              "rejection_reason": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "Who submitted and decided the week, and when (the `TimesheetWeekApproval` shape); null while approvals are off"
          },
          "total_hours": {
            "type": "string",
            "description": "The week's total, decimal hours as a string"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimesheetWeekRow"
            },
            "description": "By project name, then time on the project, then items"
          },
          "absence_types": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "archived",
                "can_log"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "archived": {
                  "type": "boolean",
                  "description": "Removed: here only while it has hours this week"
                },
                "can_log": {
                  "type": "boolean"
                }
              }
            }
          },
          "projects": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                }
              }
            },
            "description": "What a new row can be in: active projects with their Timesheet on that the person is on. Empty when the week isn't writable"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimesheetEntry"
            },
            "description": "The person's counted entries dated in the week, newest day first"
          }
        }
      },
      "TimesheetAbsenceType": {
        "type": "object",
        "required": [
          "id",
          "name",
          "position",
          "archived",
          "archived_at",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "position": {
            "type": "number"
          },
          "archived": {
            "type": "boolean"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TimesheetPickerItem": {
        "type": "object",
        "required": [
          "id",
          "type",
          "title",
          "completed",
          "parent",
          "container",
          "occurrence",
          "day",
          "app_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "todo",
              "message",
              "document",
              "upload",
              "card",
              "calendar_event"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed": {
            "type": "boolean",
            "description": "A to-do or card that's done; false for everything else"
          },
          "parent": {
            "type": [
              "object",
              "null"
            ],
            "description": "Its parent: a to-do's list, group or to-do (a subtask), a card's column, a document's or file's folder, a message's board, an event's schedule. A tool goes by the project's name for it",
            "required": [
              "id",
              "type",
              "title"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "type": {
                "type": "string"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "container": {
            "$ref": "#/components/schemas/TimesheetPickerContainer"
          },
          "occurrence": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "A repeating event's day: pass it as `occurrence` when adding the row"
          },
          "day": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "An event's day in the caller's zone; null for other items"
          },
          "app_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "TimesheetPickerContainer": {
        "type": [
          "object",
          "null"
        ],
        "description": "Where the weekly timesheet's item picker files an item: a to-do's list or group (a subtask's, its to-do's), a document's or file's folder, a card's column, a message's Message Board; null for events, which go by `day`. A tool (the To-dos for loose to-dos, the top of Docs & Files, the Message Board) goes by the project's name for it",
        "required": [
          "id",
          "type",
          "title",
          "parent"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "todos",
              "todolist",
              "folder",
              "column",
              "message_board"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "type": [
              "object",
              "null"
            ],
            "description": "The list a to-do group sits in, or the folder a subfolder sits in; null right under the tool",
            "required": [
              "id",
              "title"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "TimesheetWeekApproval": {
        "type": "object",
        "required": [
          "person",
          "week_start",
          "week_end",
          "status",
          "changed_since",
          "submitted_at",
          "submitted_by",
          "decided_at",
          "decided_by",
          "self_approved",
          "rejection_reason"
        ],
        "properties": {
          "person": {
            "$ref": "#/components/schemas/ProjectMember"
          },
          "week_start": {
            "type": "string",
            "format": "date"
          },
          "week_end": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string",
            "enum": [
              "not_submitted",
              "submitted",
              "approved",
              "changed",
              "rejected"
            ],
            "description": "As read now: `changed` once the week's hours no longer match what was submitted or approved (an edit, a new or deleted entry, a trashed or restored item, a move; never a notes-only edit), until it's resubmitted or approved again. A rejected week stays `rejected` until it's resubmitted"
          },
          "changed_since": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "submission",
              "approval",
              null
            ],
            "description": "A changed week: whether it changed since it was submitted or since it was approved"
          },
          "submitted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "submitted_by": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "membership_id",
              "name"
            ],
            "properties": {
              "membership_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "image": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "Who submitted it: the person, or an owner or admin for them"
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was approved or rejected; null while it waits on a decision"
          },
          "decided_by": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "membership_id",
              "name"
            ],
            "properties": {
              "membership_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "image": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "Who approved or rejected it"
          },
          "self_approved": {
            "type": "boolean",
            "description": "An owner or admin approved their own week"
          },
          "rejection_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TimesheetApprovalRow": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TimesheetWeekApproval"
          },
          {
            "type": "object",
            "required": [
              "hours",
              "removed"
            ],
            "properties": {
              "hours": {
                "type": "string",
                "description": "The week's counted hours in projects the caller can see (absence included), decimal hours as a string"
              },
              "removed": {
                "type": "boolean",
                "description": "No longer on the team: their week still takes a decision"
              }
            }
          }
        ]
      },
      "TimesheetApprovals": {
        "type": "object",
        "required": [
          "week_start",
          "week_end",
          "week_starts_on",
          "weeks",
          "without_time",
          "waiting",
          "counts",
          "all_approved"
        ],
        "properties": {
          "week_start": {
            "type": "string",
            "format": "date"
          },
          "week_end": {
            "type": "string",
            "format": "date"
          },
          "week_starts_on": {
            "type": "string",
            "enum": [
              "sunday",
              "monday",
              "tuesday",
              "wednesday",
              "thursday",
              "friday",
              "saturday"
            ],
            "description": "The org's timesheet week start (Admin › Timesheets)"
          },
          "weeks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimesheetApprovalRow"
            },
            "description": "A row per person with time or a submission this week, by name"
          },
          "without_time": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectMember"
            },
            "description": "The team's current people with neither, by name"
          },
          "waiting": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimesheetApprovalRow"
            },
            "description": "Submitted or changed weeks from any other period, oldest first"
          },
          "counts": {
            "type": "object",
            "required": [
              "not_submitted",
              "submitted",
              "approved",
              "changed",
              "rejected"
            ],
            "properties": {
              "not_submitted": {
                "type": "integer"
              },
              "submitted": {
                "type": "integer"
              },
              "approved": {
                "type": "integer"
              },
              "changed": {
                "type": "integer"
              },
              "rejected": {
                "type": "integer"
              }
            },
            "description": "This week's rows by status"
          },
          "all_approved": {
            "type": "boolean",
            "description": "Everyone with time this week is approved and unchanged"
          }
        }
      }
    },
    "parameters": {
      "orgSlug": {
        "name": "orgSlug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "The organization's URL slug; list yours via GET /api/v1/authorization or /api/v1/orgs"
      },
      "page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "description": "1-based page number"
      },
      "perPage": {
        "name": "per_page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "description": "Page size, capped at 100"
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "The next page's position, opaque. Take it from a `Link: rel=\"next\"` URL and never build one; it supersedes `page`."
      }
    },
    "headers": {
      "Link": {
        "schema": {
          "type": "string"
        },
        "description": "RFC 8288 `<url>; rel=\"next\"` naming the next page, with every filter of this request kept. Absent on the last page. Follow the URL as given rather than building page URLs."
      }
    },
    "responses": {
      "Unauthenticated": {
        "description": "No valid credential (invalid, expired, or revoked tokens included)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but not allowed (role, client visibility, read-scope write, session-only route)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Missing or not yours: cross-tenant probes are indistinguishable from missing",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Validation": {
        "description": "The body parsed but a field is invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PlanLimit": {
        "description": "The organization's plan cap was reached (code limit_reached)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Over the 50-requests-per-10-seconds token budget; honor Retry-After (seconds)",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
