{
  "openapi": "3.1.1",
  "info": {
    "title": "Taskwood API",
    "version": "0.8.3",
    "description": "Tenant-isolated JSON API. Bearer scopes read/write/attachments are independent. 600 requests/minute/token shared by REST and MCP; Retry-After accompanies 429. Token management and profile/account writes require browser session+CSRF. JSON exports include current schema only. Inclusive updated_since replays boundary records; hard deletes are visible through fresh snapshots. Private attachments and speech jobs are implemented; providers must be configured before transcription. Current-actor team access, assignments, comments, history and atomic CSV import are implemented. Grove seat authority requires verified test-provider quantities; actual Stripe configuration remains unavailable; webhook providers are not enabled by default. Authoritative plan limits also protect domain/CLI/MCP writes. Browser-only billing is test mode and remains unavailable until server Stripe configuration is complete. Public OAuth clients use mandatory S256 PKCE, exact resource and explicit browser consent. Refresh reuse revokes the connection; Client ID Metadata Documents and OpenID Connect are unsupported."
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "paths": {
    "/trees": {
      "get": {
        "operationId": "listTrees",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Opaque HMAC cursor bound to owner, endpoint, filters and limit; expires after 15 minutes. Creation order is stable; inserts after page one are excluded."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive boundary, including legacy second-precision and same-second edits. Deduplicate by id; hard deletions require a fresh snapshot."
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tree"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        }
      },
      "post": {
        "operationId": "createTree",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TreeInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tree": {
                      "$ref": "#/components/schemas/Tree"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "maxLength": 160
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getTree",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TreeSnapshot"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        },
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified."
      },
      "patch": {
        "operationId": "updateTree",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "minLength": 1
                  },
                  "style": {
                    "type": "string",
                    "enum": [
                      "tidy-fan",
                      "space-colonization",
                      "lsystem"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tree": {
                      "$ref": "#/components/schemas/Tree"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTree",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "get": {
        "operationId": "listProjects",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Opaque HMAC cursor bound to owner, endpoint, filters and limit; expires after 15 minutes. Creation order is stable; inserts after page one are excluded."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive boundary, including legacy second-precision and same-second edits. Deduplicate by id; hard deletions require a fresh snapshot."
          },
          {
            "name": "tree_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": ""
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        }
      },
      "post": {
        "operationId": "createProject",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "maxLength": 160
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getProject",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        },
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified."
      },
      "patch": {
        "operationId": "updateProject",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tree_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "parent_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 160,
                    "minLength": 1
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "color": {
                    "type": "string",
                    "pattern": "^#[a-fA-F0-9]{6}$"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "project": {
                      "$ref": "#/components/schemas/Project"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteProject",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tasks": {
      "get": {
        "operationId": "listTasks",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Opaque HMAC cursor bound to owner, endpoint, filters and limit; expires after 15 minutes. Creation order is stable; inserts after page one are excluded."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive boundary, including legacy second-precision and same-second edits. Deduplicate by id; hard deletions require a fresh snapshot."
          },
          {
            "name": "tree_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": ""
          },
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": ""
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "todo",
                "open",
                "done",
                "deferred",
                "archived"
              ]
            },
            "description": ""
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "items",
                    "next_cursor"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        }
      },
      "post": {
        "operationId": "createTask",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tasks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "maxLength": 160
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getTask",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged private representation, empty body."
          }
        },
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified."
      },
      "patch": {
        "operationId": "updateTask",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tree_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 500,
                    "minLength": 1
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 50000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "todo",
                      "open",
                      "done",
                      "deferred",
                      "archived"
                    ]
                  },
                  "deadline": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "ISO 8601 date or timestamp; 1–6 fractional digits accepted."
                  },
                  "color": {
                    "type": "string",
                    "pattern": "^#[a-fA-F0-9]{6}$"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTask",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/export": {
      "get": {
        "operationId": "exportTree",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": ""
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TreeSnapshot"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Full retained tasks/messages/comments are included; history follows the effective plan window. The aggregate budget is10000 contributors and8 MiB of raw text bytes, checked before materialization within one database transaction. This is not a serialized JSON byte limit. Oversized exports return413. Binary files are retrieved separately through authorized attachment endpoints; a complete SQLite/files restore uses the operator backup commands."
      }
    },
    "/search": {
      "get": {
        "operationId": "searchTasks",
        "description": "SQLite FTS5 literal terms across title, text and notes. No transcript field exists in this release. Results use stable creation order.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "minLength": 1
            },
            "description": ""
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Opaque HMAC cursor bound to owner, endpoint, filters and limit; expires after 15 minutes. Creation order is stable; inserts after page one are excluded."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive boundary, including legacy second-precision and same-second edits. Deduplicate by id; hard deletions require a fresh snapshot."
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Representation ETag; authorization is checked before 304."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Task"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tokens": {
      "get": {
        "operationId": "listPersonalTokens",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Token"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPersonalToken",
        "description": "Session and CSRF only. Idempotency-Key is rejected before creation to avoid persisting a secret. Token UI must bypass offline replay. Seed grants read and attachments scopes for reading owned content; write requires a paid plan and the write scope. Maximum 20 active tokens.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenCreated"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tokens/{id}": {
      "delete": {
        "operationId": "revokePersonalToken",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent-calls": {
      "get": {
        "operationId": "listAgentCalls",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "description": "Latest 100 owned calls. Journal retains at most 2000 metadata records per owner, with no body, search terms, note content or credentials.",
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentCall"
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "operationId": "currentUser",
        "security": [
          {},
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "anyOf": [
                        {
                          "$ref": "#/components/schemas/User"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "csrf": {
                      "type": "string",
                      "description": "Only cookie sessions return CSRF bootstrap."
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/profile": {
      "patch": {
        "operationId": "updateProfile",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Required when cookie writes include an Idempotency-Key; optional for Bearer, checked when supplied."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "7-day transactional replay per owner. Original 200/201 is preserved. Changed method/path/query/body returns 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "ru"
                    ]
                  },
                  "timezone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/register": {
      "post": {
        "operationId": "register",
        "description": "Cookie CSRF session is bootstrapped via GET auth/me. Bearer does not authorize account operations. Password reset also revokes all personal tokens and MCP sessions.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    },
                    "csrf": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 1024,
                    "writeOnly": true
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "language": {
                    "type": "string",
                    "enum": [
                      "en",
                      "ru"
                    ]
                  },
                  "timezone": {
                    "type": "string"
                  }
                },
                "required": [
                  "email",
                  "password"
                ]
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "operationId": "login",
        "description": "Cookie CSRF session is bootstrapped via GET auth/me. Bearer does not authorize account operations. Password reset also revokes all personal tokens and MCP sessions.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    },
                    "csrf": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 1024,
                    "writeOnly": true
                  }
                },
                "required": [
                  "email",
                  "password"
                ]
              }
            }
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "operationId": "logout",
        "description": "Cookie CSRF session is bootstrapped via GET auth/me. Bearer does not authorize account operations. Password reset also revokes all personal tokens and MCP sessions.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/User"
                    },
                    "csrf": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/reset/request": {
      "post": {
        "operationId": "reset_request",
        "description": "Session CSRF required. Request is queued; delivery is not claimed when SMTP is unconfigured.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/reset/confirm": {
      "post": {
        "operationId": "reset_confirm",
        "description": "Session CSRF required. Request is queued; delivery is not claimed when SMTP is unconfigured.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "password": {
                    "type": "string",
                    "minLength": 10,
                    "writeOnly": true
                  }
                },
                "required": [
                  "token",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/verify/request": {
      "post": {
        "operationId": "verify_request",
        "description": "Session CSRF required. Request is queued; delivery is not claimed when SMTP is unconfigured.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/verify/confirm": {
      "post": {
        "operationId": "verify_confirm",
        "description": "Session CSRF required. Request is queued; delivery is not claimed when SMTP is unconfigured.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "writeOnly": true
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "servers": [
        {
          "url": "/"
        }
      ],
      "post": {
        "operationId": "mcpStreamableHttp",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "description": "MCP stable 2025-11-25 (compatible base subset 2025-06-18/2025-03-26). POST accepts one JSON-RPC object; notification/response returns empty 202. Initialize returns Mcp-Session-Id, then notifications/initialized; session bound to token, expires after one hour. No SSE, OAuth discovery, attachments or speech tools are claimed.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "application/json, text/event-stream"
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "2025-11-25",
                "2025-06-18",
                "2025-03-26"
              ]
            },
            "description": ""
          },
          {
            "name": "Mcp-Session-Id",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Required after initialize."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {
                    "type": [
                      "string",
                      "integer"
                    ]
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or protocol/tool error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Accepted notification/response, empty body."
          },
          "default": {
            "description": "Transport/authorization error"
          }
        }
      },
      "get": {
        "operationId": "mcpSseUnavailable",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "405": {
            "description": "SSE not offered; use POST."
          }
        }
      },
      "delete": {
        "operationId": "closeMcpSession",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "Mcp-Session-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": ""
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Session closed, empty body."
          }
        }
      }
    },
    "/tasks/{task}/attachments": {
      "get": {
        "operationId": "listAttachments",
        "description": "Requires read+attachments for Bearer. Lists owned active metadata (max100/task).",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Attachment"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "task",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      },
      "post": {
        "operationId": "uploadAttachment",
        "description": "Requires write+attachments for Bearer or browser CSRF. JSON response contains unsigned URLs; supports stable Idempotency-Key with content hash.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "attachment": {
                      "$ref": "#/components/schemas/Attachment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "task",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "One PHP upload, maximum26,214,400 bytes."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/tasks/{task}/messages": {
      "get": {
        "operationId": "listTaskMessages",
        "description": "Requires read+attachments for Bearer. Latest200 messages in chronological order.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaskMessage"
                      },
                      "maxItems": 200
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "task",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      }
    },
    "/attachments/{id}": {
      "get": {
        "operationId": "getAttachment",
        "description": "Requires read+attachments. Owner-scoped metadata.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "attachment": {
                      "$ref": "#/components/schemas/Attachment"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      },
      "delete": {
        "operationId": "deleteAttachment",
        "description": "Requires write+attachments. Soft-deletes content and cancels pending speech reservations; retained bytes remain charged until cleanup.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      }
    },
    "/attachments/{id}/link": {
      "get": {
        "operationId": "getAttachmentDownloadLink",
        "description": "Requires read+attachments. Short-lived HMAC bound to owner/id/SHA/expiry, TTL≤300 seconds. Do not persist links in write idempotency journals.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedDownload"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      }
    },
    "/attachments/{id}/content": {
      "get": {
        "operationId": "downloadAttachment",
        "description": "Authenticated owner or exact signed owner/expires/signature query. Binary streaming, attachment disposition, nosniff, private no-store; no JSON wrapper. Single byte ranges only.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "SignedContentSignature": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          },
          {
            "name": "Range",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Single bytes=start-end, start-, or -suffix."
          },
          {
            "name": "If-Range",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          },
          {
            "name": "expires",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          },
          {
            "name": "signature",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          }
        ],
        "responses": {
          "200": {
            "description": "Full stored content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Selected byte range",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "304": {
            "description": "ETag matches; empty body"
          },
          "416": {
            "description": "Unsupported or unsatisfiable range; empty body, Content-Range bytes */size"
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        }
      },
      "head": {
        "operationId": "headAttachment",
        "description": "Same authorization and range headers; body is always empty.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "SignedContentSignature": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          },
          {
            "name": "Range",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Single bytes=start-end, start-, or -suffix."
          },
          {
            "name": "If-Range",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          },
          {
            "name": "expires",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          },
          {
            "name": "signature",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "All three fields are required for preauth signed content; no other query keys accepted."
          }
        ],
        "responses": {
          "200": {
            "description": "Full stored content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Selected byte range",
            "headers": {
              "Content-Range": {
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "304": {
            "description": "ETag matches; empty body"
          },
          "416": {
            "description": "Unsupported or unsatisfiable range; empty body, Content-Range bytes */size"
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        }
      }
    },
    "/speech/transcribe": {
      "post": {
        "operationId": "transcribeAudio",
        "description": "Requires write+attachments for Bearer or browser CSRF. Creates job only with real configured provider/audio tools and available quota. Multipart saves audio and creates a voice task in a project if task_id is omitted. JSON uses existing owned attachment. Actual duration≤1h, at most3 attempts per attachment.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "202": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job"
                  ],
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/SpeechJob"
                    },
                    "attachment": {
                      "$ref": "#/components/schemas/Attachment"
                    },
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "200": {
            "description": "Existing completed job; no double charging",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/SpeechJob"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "speech_unavailable: no provider request, job or fake transcript",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "attachment_id"
                ],
                "properties": {
                  "attachment_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "replace_title": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "additionalProperties": false
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "anyOf": [
                  {
                    "required": [
                      "task_id"
                    ]
                  },
                  {
                    "required": [
                      "project_id"
                    ]
                  }
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "task_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "project_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "tree_id": {
                    "type": "string",
                    "maxLength": 160
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/speech/jobs/{id}": {
      "get": {
        "operationId": "getSpeechJob",
        "description": "Requires read+attachments; owner-scoped job state.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job": {
                      "$ref": "#/components/schemas/SpeechJob"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ]
      }
    },
    "/speech/status": {
      "get": {
        "operationId": "getSpeechStatus",
        "description": "Requires read+attachments; truthful configuration without credentials, plus UTC monthly usage.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available": {
                      "type": "boolean"
                    },
                    "configured": {
                      "type": "boolean"
                    },
                    "audio_processing": {
                      "type": "boolean"
                    },
                    "providers": {
                      "type": "array",
                      "items": {
                        "enum": [
                          "qwen",
                          "elevenlabs"
                        ]
                      }
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "string",
                          "pattern": "^[0-9]{4}-[0-9]{2}$"
                        },
                        "used_seconds": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "reserved_seconds": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "limit_seconds": {
                          "type": "integer",
                          "minimum": 0
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        }
      }
    },
    "/attachments": {
      "post": {
        "operationId": "createTaskWithAttachment",
        "description": "Atomically create an owned task and its attachment from a project composer. Requires write+attachments for Bearer or browser CSRF. The filename is the default title; invalid upload rolls back the task. Stable multipart Idempotency-Key retries return the same task/file.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "attachment": {
                      "$ref": "#/components/schemas/Attachment"
                    },
                    "task": {
                      "$ref": "#/components/schemas/Task"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "403": {
            "description": "Scope or CSRF denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "404": {
            "description": "Owned resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "413": {
            "description": "Upload exceeds25 MiB or bounded request size",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported content/file type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid image/audio/fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          },
          "409": {
            "description": "Quota exceeded or conflicting replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "One PHP upload, maximum26,214,400 bytes."
                  },
                  "tree_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/push": {
      "get": {
        "operationId": "notificationState",
        "description": "Browser-session notification state and devices.",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushState"
                }
              }
            }
          },
          "401": {
            "description": "Sign in first."
          },
          "403": {
            "description": "Browser session and same-origin CSRF are required; personal tokens cannot manage devices."
          },
          "409": {
            "description": "Owner changed or subscription belongs to another account."
          },
          "422": {
            "description": "Invalid input or plan restriction."
          },
          "503": {
            "description": "Push server is awaiting configuration."
          }
        }
      }
    },
    "/push/preferences": {
      "patch": {
        "operationId": "notificationPreferences",
        "description": "Update local-time quiet hours/digest. Deadline and inactivity reminders require an effective paid plan. Assigned colleague reminders require explicit opt-in and fresh current membership plus owner verified Grove seat authority before enqueue and delivery.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sign in first."
          },
          "403": {
            "description": "Browser session and same-origin CSRF are required; personal tokens cannot manage devices."
          },
          "409": {
            "description": "Owner changed or subscription belongs to another account."
          },
          "422": {
            "description": "Invalid input or plan restriction."
          },
          "503": {
            "description": "Push server is awaiting configuration."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushPreferences"
              }
            }
          }
        }
      }
    },
    "/push/subscribe": {
      "post": {
        "operationId": "subscribeDevice",
        "description": "Register this browser only after its explicit permission gesture. Endpoint capabilities are encrypted in SQLite.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sign in first."
          },
          "403": {
            "description": "Browser session and same-origin CSRF are required; personal tokens cannot manage devices."
          },
          "409": {
            "description": "Owner changed or subscription belongs to another account."
          },
          "422": {
            "description": "Invalid input or plan restriction."
          },
          "503": {
            "description": "Push server is awaiting configuration."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subscription"
                ],
                "properties": {
                  "subscription": {
                    "type": "object"
                  },
                  "device_name": {
                    "type": "string",
                    "maxLength": 80
                  }
                }
              }
            }
          }
        }
      }
    },
    "/push/device": {
      "post": {
        "operationId": "currentNotificationDevice",
        "description": "Look up the current browser endpoint without exposing other capabilities.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sign in first."
          },
          "403": {
            "description": "Browser session and same-origin CSRF are required; personal tokens cannot manage devices."
          },
          "409": {
            "description": "Owner changed or subscription belongs to another account."
          },
          "422": {
            "description": "Invalid input or plan restriction."
          },
          "503": {
            "description": "Push server is awaiting configuration."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "maxLength": 2048
                  }
                }
              }
            }
          }
        }
      }
    },
    "/push/unsubscribe": {
      "post": {
        "operationId": "disconnectDevice",
        "description": "Disconnect an owned device by id or current browser endpoint.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sign in first."
          },
          "403": {
            "description": "Browser session and same-origin CSRF are required; personal tokens cannot manage devices."
          },
          "409": {
            "description": "Owner changed or subscription belongs to another account."
          },
          "422": {
            "description": "Invalid input or plan restriction."
          },
          "503": {
            "description": "Push server is awaiting configuration."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "endpoint": {
                    "type": "string",
                    "maxLength": 2048
                  }
                },
                "minProperties": 1
              }
            }
          }
        }
      }
    },
    "/billing": {
      "get": {
        "operationId": "getBillingState",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Bind this browser request to the original signed-in user; a changed account returns409."
          }
        ],
        "description": "Private no-store account policy and test billing state. No provider call is made by this read.",
        "responses": {
          "200": {
            "description": "Current account billing state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "configured",
                    "mode",
                    "policy",
                    "prices",
                    "countries",
                    "subscriptions",
                    "invoices",
                    "seats"
                  ],
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "mode": {
                      "const": "test"
                    },
                    "policy": {
                      "type": "object",
                      "description": "Effective verified plan, trial end, authoritative limits/usage/remaining counts."
                    },
                    "prices": {
                      "type": "object",
                      "description": "Server-owned Sprout/Grove amounts in USD/EUR minor units and exact annual saving percentages."
                    },
                    "countries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "pattern": "^[A-Z]{2}$"
                          },
                          "currency": {
                            "enum": [
                              "usd",
                              "eur"
                            ]
                          }
                        }
                      }
                    },
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "invoices": {
                      "type": "array",
                      "maxItems": 50,
                      "items": {
                        "type": "object"
                      }
                    },
                    "customer": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "pending_checkout": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "seats": {
                      "$ref": "#/components/schemas/GroveSeats"
                    },
                    "offers": {
                      "$ref": "#/components/schemas/BillingOffers"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "A browser session is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Personal tokens cannot manage billing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or generic Idempotency-Key prohibited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Test Stripe billing is awaiting server configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/billing/checkout": {
      "post": {
        "operationId": "createTestCheckout",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Bind this browser request to the original signed-in user; a changed account returns409."
          }
        ],
        "description": "Test mode only; private/no-store. Retry an unknown result with the same body request_id. Generic Idempotency-Key is prohibited so hosted redirect URLs never enter the replay journal.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "plan": {
                    "enum": [
                      "sprout",
                      "grove"
                    ]
                  },
                  "interval": {
                    "enum": [
                      "month",
                      "year"
                    ]
                  },
                  "billing_country": {
                    "type": "string",
                    "pattern": "^[A-Z]{2}$"
                  },
                  "request_id": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 128,
                    "pattern": "^[A-Za-z0-9_-]+$"
                  },
                  "seats": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 1,
                    "description": "Grove seats including the owner. Other plans accept only one. No quantity update is performed for an existing subscription."
                  },
                  "student_discount": {
                    "type": "boolean",
                    "default": false,
                    "description": "Explicit new-checkout selection. Fresh owned manual approval and retrieved test50%-forever coupon required. Does not allow promotion-code stacking. Existing checkout fields and Grove quantity remain authoritative."
                  }
                },
                "required": [
                  "plan",
                  "interval",
                  "billing_country",
                  "request_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved hosted Stripe test redirect",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "mode": {
                      "const": "test"
                    }
                  },
                  "required": [
                    "url",
                    "mode"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflicting or existing checkout/subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Test provider request failed; retry the same request_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "A browser session is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Personal tokens cannot manage billing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or generic Idempotency-Key prohibited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Test Stripe billing is awaiting server configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "operationId": "createTestPortal",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Bind this browser request to the original signed-in user; a changed account returns409."
          }
        ],
        "description": "Test mode only; private/no-store. Retry an unknown result with the same body request_id. Generic Idempotency-Key is prohibited so hosted redirect URLs never enter the replay journal.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "request_id": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 128,
                    "pattern": "^[A-Za-z0-9_-]+$"
                  }
                },
                "required": [
                  "request_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved hosted Stripe test redirect",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "mode": {
                      "const": "test"
                    }
                  },
                  "required": [
                    "url",
                    "mode"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Conflicting or existing checkout/subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Test provider request failed; retry the same request_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "A browser session is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Personal tokens cannot manage billing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or generic Idempotency-Key prohibited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Test Stripe billing is awaiting server configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/billing/webhook": {
      "post": {
        "operationId": "receiveTestStripeWebhook",
        "security": [
          {
            "StripeSignature": []
          }
        ],
        "description": "Official SDK verifies the exact raw request body and signature within300seconds. Only test events queue resource references; provider workers re-fetch current subscription truth.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified test event accepted or deduplicated"
          },
          "400": {
            "description": "Invalid signature/event or live event rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Test billing is not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/members": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "listTreeMembers",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Shared authority requires verified Grove seats; over-capacity denies all member grants without deleting data.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamMembers"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/members/{member}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        },
        {
          "name": "member",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "delete": {
        "operationId": "revokeTreeMember",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Only the tree owner may revoke a member; ownership is immutable.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/invites": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "post": {
        "operationId": "inviteTreeMember",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Shared authority requires verified Grove seats; over-capacity denies all member grants without deleting data. Browser session and CSRF required. Recipient must already have a verified account. Delivery is manual; no email is sent. Generic Idempotency-Key is prohibited.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          }
        ],
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamInvite"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamInviteInput"
              }
            }
          }
        }
      }
    },
    "/teams/invites/accept": {
      "parameters": [],
      "post": {
        "operationId": "acceptTreeInvite",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. A single-use invitation is bound to its original verified recipient. The browser strips any invitation fragment before making this private request.",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tree_id": {
                      "type": "string"
                    },
                    "role": {
                      "const": "member"
                    },
                    "seats_verified": {
                      "const": false
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invite_token": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  }
                },
                "required": [
                  "invite_token"
                ]
              }
            }
          }
        }
      }
    },
    "/tasks/{id}/assignment": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getTaskAssignment",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Assignee must currently participate in this tree; null clears the assignment.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assignment": {
                      "$ref": "#/components/schemas/TaskAssignment"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "assignTask",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Assignee must currently participate in this tree; null clears the assignment.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assignment": {
                      "$ref": "#/components/schemas/TaskAssignment"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignee_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "assignee_id"
                ]
              }
            }
          }
        }
      }
    },
    "/tasks/{id}/comments": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "listTaskComments",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": ""
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskComments"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTaskComment",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. At most1000 active comments per task.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comment": {
                      "$ref": "#/components/schemas/TaskComment"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        }
      }
    },
    "/tasks/{id}/comments/{comment}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        },
        {
          "name": "comment",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "patch": {
        "operationId": "updateTaskComment",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Only the author or tree owner may edit.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comment": {
                      "$ref": "#/components/schemas/TaskComment"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteTaskComment",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Only the author or tree owner may delete.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "comment": {
                      "type": "null"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/history": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getTreesHistory",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Seed exposes the most recent30 days; older rows remain retained.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": ""
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryPage"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tasks/{id}/history": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "get": {
        "operationId": "getTasksHistory",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Seed exposes the most recent30 days; older rows remain retained.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": ""
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": ""
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryPage"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{id}/csv/validate": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "post": {
        "operationId": "validateTreeCsv",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. This preview performs no import; at most100 row errors and10 preview rows are returned.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CsvValidation"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CsvInput"
              }
            }
          }
        }
      }
    },
    "/trees/{id}/csv/import": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": ""
        }
      ],
      "post": {
        "operationId": "importTreeCsv",
        "description": "Current access is resolved from the signed-in actor. Shared trees require the original owner’s effective Grove plan and active membership; revocation/expiry/downgrade denies access. Seat billing is not verified. Atomic all-or-nothing import. Idempotency-Key is required (16–128 characters); the same CSV replays only while the actor and every imported task remain writable. Changed CSV returns409.",
        "security": [
          {
            "PersonalToken": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "OAuthAccess": []
          }
        ],
        "parameters": [
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 160
            },
            "description": "Optional account guard; a mismatch returns 409 before data or mutation."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 16,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9:_.-]+$"
            },
            "description": "Stable replay key. Current actor, membership epoch, original owner plan and target are authorized before any cached result."
          }
        ],
        "responses": {
          "201": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CsvImport"
                }
              }
            }
          },
          "default": {
            "description": "Structured API error; current authorization is always rechecked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CsvInput"
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource/mcp": {
      "get": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthProtectedResource",
        "security": [],
        "responses": {
          "200": {
            "description": "Exact resource and authorization_servers; bearer_methods_supported header; minimum read scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthAuthorizationMetadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Issuer, endpoint URLs, public auth none, mandatory PKCE S256, iss support; CIMD false",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthRegisterPublicClient",
        "security": [],
        "description": "Unverified public DCR only. Exact HTTPS callbacks or exact native loopback HTTP port. No external fetch; max2000clients, 10 registrations/hour/IP and100global. Registration persists while its connection remains active.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthRegistration"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthClient"
                }
              }
            }
          },
          "400": {
            "description": "Invalid OAuth request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit; Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Bounded capacity unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthBeginAuthorization",
        "security": [],
        "description": "Validates registered exact redirect and resource, code and S256. Guest303 to local login; signed-in303 to local explicit-consent UI. No external redirect before owner allow/deny. No wildcard callbacks. Duplicate parameters are rejected.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "const": "code"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "const": "S256"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "303": {
            "description": "Local login/consent Location only"
          },
          "400": {
            "description": "Invalid OAuth request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit; Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Bounded capacity unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthExchangeOrRotate",
        "security": [],
        "description": "Public client_id; no Authorization/client_secret. Exact resource required. Single-use code10min and PKCE verifier; refresh rotates and reuse revokes the family. Access15min; fixed refresh family30days. Every response no-store.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "grant_type": {
                        "const": "authorization_code"
                      },
                      "client_id": {
                        "type": "string",
                        "maxLength": 160
                      },
                      "resource": {
                        "type": "string",
                        "maxLength": 2048
                      },
                      "redirect_uri": {
                        "type": "string",
                        "maxLength": 2048
                      },
                      "code": {
                        "type": "string",
                        "maxLength": 100
                      },
                      "code_verifier": {
                        "type": "string",
                        "minLength": 43,
                        "maxLength": 128
                      }
                    },
                    "required": [
                      "grant_type",
                      "client_id",
                      "resource",
                      "redirect_uri",
                      "code",
                      "code_verifier"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "properties": {
                      "grant_type": {
                        "const": "refresh_token"
                      },
                      "client_id": {
                        "type": "string",
                        "maxLength": 160
                      },
                      "resource": {
                        "type": "string",
                        "maxLength": 2048
                      },
                      "refresh_token": {
                        "type": "string",
                        "maxLength": 100
                      },
                      "scope": {
                        "type": "string",
                        "maxLength": 100
                      }
                    },
                    "required": [
                      "grant_type",
                      "client_id",
                      "resource",
                      "refresh_token"
                    ],
                    "additionalProperties": false
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthToken"
                }
              }
            }
          },
          "400": {
            "description": "Invalid OAuth request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit; Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Bounded capacity unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthRevokeByTokenProof",
        "security": [],
        "description": "RFC7009-style client_id plus owned token proof. Invalid/unknown token200. A matching access or refresh token revokes its whole connection.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "token": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "token_type_hint": {
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ]
                  }
                },
                "required": [
                  "client_id",
                  "token"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Empty body"
          },
          "400": {
            "description": "Invalid OAuth request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit; Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Bounded capacity unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/consent/{request}": {
      "get": {
        "operationId": "oauthReadConsent",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "request",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthConsent"
                }
              }
            }
          },
          "404": {
            "description": "Expired/used/foreign owner or session"
          }
        }
      },
      "post": {
        "operationId": "oauthExplicitConsentDecision",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "description": "Explicit allow/deny only. No Idempotency-Key or offline replay. Response redirect includes iss+state and code or access_denied. Root UI navigates only after confirmed owner decision.",
        "parameters": [
          {
            "name": "request",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decision": {
                    "enum": [
                      "allow",
                      "deny"
                    ]
                  }
                },
                "required": [
                  "decision"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed external callback",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redirect": {
                      "type": "string",
                      "maxLength": 4096
                    }
                  },
                  "required": [
                    "redirect"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "403": {
            "description": "CSRF failure"
          },
          "404": {
            "description": "Expired/used/foreign owner or session"
          },
          "400": {
            "description": "Invalid OAuth request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit; Retry-After",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Bounded capacity unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/grants": {
      "get": {
        "operationId": "oauthListOwnedConnections",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Safe owner connection metadata only; no tokens/hashes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OAuthGrant"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false
                }
              }
            }
          }
        }
      }
    },
    "/oauth/grants/{id}": {
      "delete": {
        "operationId": "oauthRevokeOwnedConnection",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked connection and MCP sessions"
          },
          "403": {
            "description": "CSRF failure"
          },
          "404": {
            "description": "Foreign/unknown connection"
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "servers": [
          {
            "url": "/"
          }
        ],
        "operationId": "oauthProtectedResourceAlias",
        "security": [],
        "responses": {
          "200": {
            "description": "Exact resource and authorization_servers; bearer_methods_supported header; minimum read scope",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/trees/{treeId}/appearance": {
      "get": {
        "operationId": "getTreeAppearance",
        "description": "Current canonical plan determines effective preferences. Stored settings are retained after downgrade. Read scope.",
        "parameters": [
          {
            "name": "treeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exact owned tree ID or stable slug. Shared grants require a future explicit adapter."
          }
        ],
        "responses": {
          "200": {
            "description": "appearance envelope with tree_id, stored/effective preferences, entitlements, plan and updated_at"
          },
          "404": {
            "description": "No owned tree"
          }
        },
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          },
          {
            "SessionCookie": []
          }
        ]
      },
      "patch": {
        "operationId": "updateTreeAppearance",
        "description": "Write scope/session CSRF. Non-default season/theme requires Sprout or Grove; brand colors require verified Grove. Free defaults and fruits remain available to the session owner. Idempotency authorization runs before replay.",
        "parameters": [
          {
            "name": "treeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exact owned tree ID or stable slug. Shared grants require a future explicit adapter."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "X-Taskwood-User",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TreeAppearanceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated current appearance envelope"
          },
          "403": {
            "description": "Plan, write scope or CSRF failure"
          },
          "404": {
            "description": "No owned tree"
          },
          "409": {
            "description": "Actor or idempotency conflict"
          },
          "422": {
            "description": "Invalid choice or RGB palette"
          }
        },
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          },
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ]
      }
    },
    "/trees/{treeId}/archive": {
      "get": {
        "operationId": "listTreeArchive",
        "description": "Current done/archived tasks with full actual IDs, newest saved first. Seed archive view includes the last 30 days; all older/undated records remain in ordinary reads and JSON export. Paid archive view has no time limit. 100 rows by default, max200; follow every next_cursor. Concurrent mutations invalidate the cursor rather than mixing pages. Read scope.",
        "parameters": [
          {
            "name": "treeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Exact owned tree ID or stable slug. Shared grants require a future explicit adapter."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 1024
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "done",
                "archived"
              ],
              "default": "all"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "items[], next_cursor, tree_id, history_days, window_start, retained_total/older/undated, data_retained=true, export_url, upgrade_url"
          },
          "404": {
            "description": "No owned tree"
          },
          "409": {
            "description": "archive_changed; refresh first page"
          },
          "422": {
            "description": "Invalid/expired/foreign/downgraded cursor or limit"
          }
        },
        "security": [
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": []
          },
          {
            "SessionCookie": []
          }
        ]
      }
    },
    "/connectors": {
      "get": {
        "summary": "Authenticated configuration flags and implemented capabilities; no public status endpoint",
        "security": [
          {
            "SessionCookie": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      }
    },
    "/connectors/errors": {
      "get": {
        "summary": "Last 30 sanitized connector errors owned by the caller",
        "security": [
          {
            "SessionCookie": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      }
    },
    "/webhooks": {
      "get": {
        "summary": "List owned webhook metadata; private URL and signing secret are never returned",
        "security": [
          {
            "SessionCookie": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      },
      "post": {
        "summary": "Create an owned HTTPS webhook",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "201": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "url",
                  "secret"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "writeOnly": true,
                    "description": "HTTPS port443, public DNS only; no credentials, fragments or redirects"
                  },
                  "secret": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{32,256}$",
                    "writeOnly": true
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 3,
                    "uniqueItems": true,
                    "items": {
                      "enum": [
                        "task.created",
                        "task.done",
                        "project.created"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Read one owned webhook",
        "security": [
          {
            "SessionCookie": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      },
      "patch": {
        "summary": "Enable or disable; cancellation fences old deliveries",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete own webhook and deliveries",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported."
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Send connector.test and return only ok/http_status/error_code",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported."
      }
    },
    "/connectors/telegram": {
      "get": {
        "summary": "Own connection metadata and last 30 update states; no chat IDs or payloads",
        "security": [
          {
            "SessionCookie": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      },
      "delete": {
        "summary": "Disconnect and fence queued or running updates and notifications",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported."
      }
    },
    "/connectors/telegram/link": {
      "post": {
        "summary": "Create a single-use 10-minute manual /start code; reject Idempotency-Key",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "201": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. This endpoint is session-only and rejects Idempotency-Key before creating the single-use link code.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "tree_id"
                ],
                "properties": {
                  "tree_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/connectors/telegram/test": {
      "post": {
        "summary": "Telegram getMe health check; never sends a message",
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          },
          {
            "PersonalToken": []
          },
          {
            "OAuthAccess": [
              "write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "write",
        "description": "Browser writes require X-CSRF-Token. Any X-Taskwood-User must match the authenticated actor; replayed cookie writes also require that header. Bearer writes require current paid entitlement and write scope. Ownership and entitlement are rechecked before persisted replay; secrets are never returned from metadata. Idempotency-Key is supported."
      }
    },
    "/connectors/telegram/callback": {
      "post": {
        "summary": "Telegram private-chat callback before browser/PAT authentication",
        "security": [
          {
            "TelegramCallbackSecret": []
          }
        ],
        "description": "Exact route and POST only. Constant-time secret header check. Unsupported and duplicate updates are acknowledged; unconfigured speech returns503. No owner-wide delegated shared-tree access.",
        "responses": {
          "200": {
            "description": "Accepted, duplicate or ignored update"
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "update_id"
                ],
                "properties": {
                  "update_id": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "message": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/admin/overview": {
      "get": {
        "summary": "Private administrative overview; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin"
      }
    },
    "/admin/users": {
      "get": {
        "summary": "Private administrative users; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/admin/trees": {
      "get": {
        "summary": "Private administrative trees; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/admin/connectors": {
      "get": {
        "summary": "Private administrative connectors; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/admin/queue": {
      "get": {
        "summary": "Private administrative queue; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/admin/errors": {
      "get": {
        "summary": "Private administrative errors; current database role is checked",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful sanitized result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connector_failures": {
                      "$ref": "#/components/schemas/AdminConnectorFailures"
                    }
                  },
                  "required": [
                    "connector_failures"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read",
        "x-taskwood-admin-role": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/admin/metrics.csv": {
      "get": {
        "summary": "Private aggregate CSV; formula-safe cells; verified SQL values only",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregate metrics; unavailable MRR is empty, never invented",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "403": {
            "description": "Scope, plan or administrator access denied"
          },
          "404": {
            "description": "Owned resource not found"
          },
          "422": {
            "description": "Invalid input; one-time link rejects Idempotency-Key"
          },
          "429": {
            "description": "Rate limited; Retry-After"
          },
          "503": {
            "description": "Connector or speech configuration unavailable"
          }
        },
        "x-taskwood-scope": "read"
      }
    },
    "/billing/offers": {
      "get": {
        "summary": "Read honest offer configuration and your own student request",
        "tags": [
          "Billing offers"
        ],
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingOffers"
                }
              }
            }
          },
          "401": {
            "description": "Sign in with a browser session."
          },
          "403": {
            "description": "The session, CSRF, account role or eligibility does not permit this operation."
          },
          "409": {
            "description": "Account/request/version changed; refresh or retry the same original request."
          },
          "422": {
            "description": "Invalid or unsupported input. Idempotency-Key is not accepted for billing writes."
          },
          "502": {
            "description": "The configured test price or coupon could not be verified."
          },
          "503": {
            "description": "Test billing or this feature is awaiting configuration."
          }
        }
      }
    },
    "/billing/quote": {
      "get": {
        "summary": "Verify a server-selected test price and optional approved student discount",
        "tags": [
          "Billing offers"
        ],
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingQuote"
                }
              }
            }
          },
          "401": {
            "description": "Sign in with a browser session."
          },
          "403": {
            "description": "The session, CSRF, account role or eligibility does not permit this operation."
          },
          "409": {
            "description": "Account/request/version changed; refresh or retry the same original request."
          },
          "422": {
            "description": "Invalid or unsupported input. Idempotency-Key is not accepted for billing writes."
          },
          "502": {
            "description": "The configured test price or coupon could not be verified."
          },
          "503": {
            "description": "Test billing or this feature is awaiting configuration."
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "plan",
            "required": true,
            "schema": {
              "enum": [
                "sprout",
                "grove"
              ]
            }
          },
          {
            "in": "query",
            "name": "interval",
            "required": true,
            "schema": {
              "enum": [
                "month",
                "year"
              ]
            }
          },
          {
            "in": "query",
            "name": "billing_country",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            }
          },
          {
            "in": "query",
            "name": "seats",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "student_discount",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "description": "Country is explicit customer input, validated by the server against supported countries. Regional choices require configured prices lower than the retrieved base price, matching currency/interval/product and cis metadata. Checkout persists then re-reads the owned Stripe customer country and re-verifies prices/coupon. The quote is an estimate before applicable Checkout tax. No arbitrary amount/currency/price/coupon/redirect is accepted. Sprout seats must be one."
      }
    },
    "/billing/student/requests": {
      "post": {
        "summary": "Submit your own short student request (internal request_id replay)",
        "tags": [
          "Billing offers"
        ],
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentRequestResult"
                }
              }
            }
          },
          "401": {
            "description": "Sign in with a browser session."
          },
          "403": {
            "description": "The session, CSRF, account role or eligibility does not permit this operation."
          },
          "409": {
            "description": "Account/request/version changed; refresh or retry the same original request."
          },
          "422": {
            "description": "Invalid or unsupported input. Idempotency-Key is not accepted for billing writes."
          },
          "502": {
            "description": "The configured test price or coupon could not be verified."
          },
          "503": {
            "description": "Test billing or this feature is awaiting configuration."
          },
          "429": {
            "description": "At most three requests per account per 30 days; only one pending or unexpired approved request."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "request_id",
                  "note"
                ],
                "properties": {
                  "request_id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{16,128}$"
                  },
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Minimal short text; do not send identity documents or payment details."
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List up to 50 pending requests for manual admin review",
        "tags": [
          "Billing offers"
        ],
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentPendingRequests"
                }
              }
            }
          },
          "401": {
            "description": "Sign in with a browser session."
          },
          "403": {
            "description": "The session, CSRF, account role or eligibility does not permit this operation."
          },
          "409": {
            "description": "Account/request/version changed; refresh or retry the same original request."
          },
          "422": {
            "description": "Invalid or unsupported input. Idempotency-Key is not accepted for billing writes."
          },
          "502": {
            "description": "The configured test price or coupon could not be verified."
          },
          "503": {
            "description": "Test billing or this feature is awaiting configuration."
          }
        },
        "description": "Fresh canonical administrator role required. Manual operator action only; no existing subscription is modified."
      }
    },
    "/billing/student/requests/{id}/decision": {
      "post": {
        "summary": "Manually approve, reject or revoke the current student request version",
        "tags": [
          "Billing offers"
        ],
        "security": [
          {
            "SessionCookie": [],
            "Csrf": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StudentRequestResult"
                }
              }
            }
          },
          "401": {
            "description": "Sign in with a browser session."
          },
          "403": {
            "description": "The session, CSRF, account role or eligibility does not permit this operation."
          },
          "409": {
            "description": "Account/request/version changed; refresh or retry the same original request."
          },
          "422": {
            "description": "Invalid or unsupported input. Idempotency-Key is not accepted for billing writes."
          },
          "502": {
            "description": "The configured test price or coupon could not be verified."
          },
          "503": {
            "description": "Test billing or this feature is awaiting configuration."
          }
        },
        "description": "Fresh canonical administrator role required. Manual operator action only; no existing subscription is modified.",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "decision",
                  "version",
                  "reference"
                ],
                "properties": {
                  "decision": {
                    "enum": [
                      "approve",
                      "reject",
                      "revoke"
                    ]
                  },
                  "version": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "reference": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Bounded manual verification reference, stored privately and absent from response."
                  },
                  "expires_at": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Approval eligibility UTC expiry; optional, future and within ten years. Only approve accepts an expiry. The coupon remains a 50%-forever contract for a resulting existing subscription."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PersonalToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Personal API token"
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "taskwood_session"
      },
      "Csrf": {
        "type": "apiKey",
        "in": "header",
        "name": "X-CSRF-Token"
      },
      "SignedContentSignature": {
        "type": "apiKey",
        "in": "query",
        "name": "signature",
        "description": "Only attachment GET/HEAD content; HMAC additionally binds owner and expires query values."
      },
      "StripeSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "Stripe-Signature"
      },
      "OAuthAccess": {
        "type": "oauth2",
        "description": "Opaque OAuth tokens authenticate the same owner/scopes as personal tokens. resource must exactly equal the protected-resource metadata resource in authorization and token requests.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth/authorize",
            "tokenUrl": "/oauth/token",
            "refreshUrl": "/oauth/token",
            "scopes": {
              "read": "Read authorized trees and tasks as the original actor",
              "write": "Mutate authorized trees and tasks; verified paid entitlement required",
              "attachments": "Access authorized photos/audio in conjunction with read or write"
            }
          }
        }
      },
      "TelegramCallbackSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Telegram-Bot-Api-Secret-Token"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "TreeInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "minLength": 1
          },
          "style": {
            "type": "string",
            "enum": [
              "tidy-fan",
              "space-colonization",
              "lsystem"
            ]
          }
        },
        "required": [
          "name"
        ]
      },
      "ProjectInput": {
        "type": "object",
        "properties": {
          "tree_id": {
            "type": "string",
            "maxLength": 160
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 160,
            "minLength": 1
          },
          "description": {
            "type": "string",
            "maxLength": 10000
          },
          "side": {
            "type": "string",
            "enum": [
              "left",
              "right"
            ]
          },
          "color": {
            "type": "string",
            "pattern": "^#[a-fA-F0-9]{6}$"
          },
          "position": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "tree_id",
          "name"
        ]
      },
      "TaskInput": {
        "type": "object",
        "properties": {
          "tree_id": {
            "type": "string",
            "maxLength": 160
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1
          },
          "text": {
            "type": "string",
            "maxLength": 10000
          },
          "notes": {
            "type": "string",
            "maxLength": 50000
          },
          "status": {
            "type": "string",
            "enum": [
              "todo",
              "open",
              "done",
              "deferred",
              "archived"
            ]
          },
          "deadline": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 date or timestamp; 1–6 fractional digits accepted."
          },
          "color": {
            "type": "string",
            "pattern": "^#[a-fA-F0-9]{6}$"
          },
          "position": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "tree_id",
          "title"
        ]
      },
      "Tree": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "slug": {
            "type": "string",
            "maxLength": 160
          },
          "style": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_tasks": {
            "type": "integer"
          },
          "done_tasks": {
            "type": "integer"
          },
          "progress": {
            "type": "object"
          },
          "sharing": {
            "$ref": "#/components/schemas/Sharing"
          }
        },
        "required": [
          "id",
          "name",
          "slug"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "tree_id": {
            "type": "string",
            "maxLength": 160
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 160
          },
          "slug": {
            "type": "string",
            "maxLength": 160
          },
          "description": {
            "type": "string",
            "maxLength": 10000
          },
          "side": {
            "type": "string",
            "enum": [
              "left",
              "right"
            ]
          },
          "color": {
            "type": "string",
            "maxLength": 7
          },
          "position": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "tree_id": {
            "type": "string",
            "maxLength": 160
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 500
          },
          "text": {
            "type": "string",
            "maxLength": 10000
          },
          "notes": {
            "type": "string",
            "maxLength": 50000
          },
          "status": {
            "type": "string",
            "enum": [
              "todo",
              "done",
              "deferred",
              "archived"
            ]
          },
          "done": {
            "type": "boolean"
          },
          "deadline": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": "string",
            "maxLength": 7
          },
          "position": {
            "type": "integer"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "transcript": {
            "type": "string"
          },
          "transcript_summary": {
            "type": "string"
          },
          "speech_status": {
            "enum": [
              "none",
              "queued",
              "running",
              "done",
              "failed"
            ]
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attachment"
            }
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskMessage"
            },
            "maxItems": 200
          },
          "attachments_access": {
            "const": "scope_required",
            "description": "Only present when Bearer lacks attachments grant; arrays then omitted."
          },
          "assignment": {
            "$ref": "#/components/schemas/TaskAssignment"
          },
          "comments_count": {
            "type": "integer",
            "minimum": 0
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Actual saved archive transition time; legacy unknown dates remain null."
          }
        },
        "required": [
          "id",
          "tree_id",
          "title",
          "status"
        ]
      },
      "TreeSnapshot": {
        "type": "object",
        "properties": {
          "tree": {
            "$ref": "#/components/schemas/Tree"
          },
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Project"
            }
          },
          "tasks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Task"
            }
          },
          "progress": {
            "type": "object"
          },
          "sharing": {
            "$ref": "#/components/schemas/Sharing"
          }
        },
        "required": [
          "tree",
          "projects",
          "tasks"
        ]
      },
      "Token": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "prefix": {
            "type": "string",
            "maxLength": 15
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "read",
                "write",
                "attachments"
              ]
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "integer",
              "null"
            ]
          },
          "revoked_at": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_used_at": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "TokenInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "minLength": 1
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "read",
                "write",
                "attachments"
              ]
            }
          },
          "expires_in_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "default": 90
          }
        },
        "required": [
          "name"
        ]
      },
      "TokenCreated": {
        "type": "object",
        "properties": {
          "token": {
            "$ref": "#/components/schemas/Token"
          },
          "secret": {
            "type": "string",
            "writeOnly": false,
            "description": "Returned once to the signed-in owner. Never stored in plaintext or in an idempotency record."
          }
        },
        "required": [
          "token",
          "secret"
        ]
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "ru"
            ]
          },
          "timezone": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "enum": [
              "seed",
              "sprout",
              "grove"
            ]
          },
          "trial_ends_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "email_verified_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "admin"
            ]
          }
        }
      },
      "AgentCall": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "token_id": {
            "type": "string",
            "maxLength": 160
          },
          "transport": {
            "type": "string",
            "enum": [
              "rest",
              "mcp"
            ]
          },
          "operation": {
            "type": "string",
            "maxLength": 100
          },
          "resource_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Attachment": {
        "type": "object",
        "required": [
          "id",
          "task_id",
          "tree_id",
          "filename",
          "mime_type",
          "byte_size",
          "sha256",
          "metadata",
          "created_at",
          "content_url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "tree_id": {
            "type": "string"
          },
          "filename": {
            "type": "string",
            "maxLength": 180
          },
          "mime_type": {
            "type": "string"
          },
          "byte_size": {
            "type": "integer",
            "minimum": 1,
            "maximum": 26214400
          },
          "sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "metadata": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "width": {
                    "type": "integer"
                  },
                  "height": {
                    "type": "integer"
                  },
                  "orientation": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 8
                  }
                }
              },
              {
                "type": "array",
                "maxItems": 0
              }
            ],
            "description": "Safe image dimensions/orientation; other media empty. No GPS or filesystem paths."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "content_url": {
            "type": "string",
            "description": "Unsigned private URL requires normal owner authentication."
          },
          "speech": {
            "type": "object",
            "description": "Optional persisted speech job reference allows polling after reload.",
            "properties": {
              "job_id": {
                "type": "string"
              },
              "status": {
                "enum": [
                  "queued",
                  "running",
                  "done",
                  "failed"
                ]
              },
              "duration_seconds": {
                "type": "integer",
                "minimum": 1,
                "maximum": 3600
              }
            }
          }
        }
      },
      "SignedDownload": {
        "type": "object",
        "required": [
          "url",
          "expires_at"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": "integer",
            "description": "UTC epoch; no more than300 seconds from issue."
          }
        }
      },
      "TaskMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "text",
              "attachment",
              "transcript",
              "status"
            ]
          },
          "text": {
            "type": "string"
          },
          "attachment_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SpeechJob": {
        "type": "object",
        "required": [
          "id",
          "task_id",
          "attachment_id",
          "status",
          "duration_seconds",
          "transcript",
          "summary",
          "provider",
          "error_code",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "attachment_id": {
            "type": "string"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "done",
              "failed"
            ]
          },
          "duration_seconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600
          },
          "transcript": {
            "type": "string",
            "maxLength": 50000
          },
          "summary": {
            "type": "string",
            "maxLength": 4000
          },
          "provider": {
            "type": "string"
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContentError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": [
                  "object",
                  "array"
                ]
              }
            }
          }
        }
      },
      "PushPreferences": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "due_enabled": {
            "type": "boolean"
          },
          "digest_enabled": {
            "type": "boolean"
          },
          "inactivity_enabled": {
            "type": "boolean"
          },
          "quiet_enabled": {
            "type": "boolean"
          },
          "quiet_start": {
            "type": "string",
            "pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
          },
          "quiet_end": {
            "type": "string",
            "pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
          },
          "digest_time": {
            "type": "string",
            "pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
          },
          "timezone": {
            "type": "string"
          },
          "inactivity_days": {
            "type": "integer",
            "minimum": 3,
            "maximum": 30
          },
          "colleague_due_enabled": {
            "type": "boolean",
            "default": false,
            "description": "Explicit opt-in to assigned tasks in currently accessible shared trees. Recipient paid plan is not required."
          }
        }
      },
      "PushState": {
        "type": "object",
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "public_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public VAPID application key; private key and subscription endpoints never appear here."
          },
          "preferences": {
            "$ref": "#/components/schemas/PushPreferences"
          },
          "devices": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "features": {
            "type": "object",
            "properties": {
              "due": {
                "type": "boolean"
              },
              "inactivity": {
                "type": "boolean"
              },
              "colleague_due": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Sharing": {
        "type": "object",
        "properties": {
          "actor_id": {
            "type": "string"
          },
          "owner_id": {
            "type": "string"
          },
          "tree_id": {
            "type": "string"
          },
          "role": {
            "enum": [
              "owner",
              "member"
            ]
          },
          "seats_verified": {
            "const": false
          },
          "membership_epoch": {
            "type": "integer"
          },
          "task_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "actor_id",
          "owner_id",
          "tree_id",
          "role",
          "membership_epoch",
          "seats_verified"
        ]
      },
      "TeamMember": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "enum": [
              "owner",
              "member"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "expires_at": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "role",
          "active"
        ]
      },
      "TeamMembers": {
        "type": "object",
        "properties": {
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TeamMember"
            }
          },
          "sharing_available": {
            "type": "boolean"
          },
          "seats_verified": {
            "type": "boolean"
          },
          "seats": {
            "$ref": "#/components/schemas/GroveSeats"
          }
        },
        "required": [
          "members",
          "sharing_available",
          "seats_verified"
        ]
      },
      "TeamInviteInput": {
        "type": "object",
        "properties": {
          "recipient_id": {
            "type": "string"
          },
          "expires_at": {
            "type": "integer",
            "description": "Unix time, future and at most seven days from now; defaults to one day."
          },
          "member_expires_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Optional Unix time, future and at most 366 days from now."
          }
        },
        "required": [
          "recipient_id"
        ]
      },
      "TeamInvite": {
        "type": "object",
        "properties": {
          "invite": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "tree_id": {
                "type": "string"
              },
              "recipient_id": {
                "type": "string"
              },
              "expires_at": {
                "type": "integer"
              },
              "member_expires_at": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "invite_token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$",
            "description": "One-time manual invitation secret. Never put it in query parameters, storage or logs."
          },
          "delivery": {
            "const": "manual"
          },
          "seats_verified": {
            "const": false
          }
        },
        "required": [
          "invite",
          "invite_token",
          "delivery",
          "seats_verified"
        ]
      },
      "TaskAssignment": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "assignee_id": {
            "type": "string"
          },
          "assigned_by": {
            "type": "string"
          },
          "assigned_at": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "TaskComment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "actor_id": {
            "type": "string"
          },
          "body": {
            "type": "string",
            "maxLength": 10000
          },
          "sequence": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "task_id",
          "actor_id",
          "body",
          "created_at",
          "updated_at"
        ]
      },
      "TaskComments": {
        "type": "object",
        "properties": {
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskComment"
            }
          },
          "next_after": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "comments",
          "next_after"
        ]
      },
      "HistoryEvent": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "entity_type": {
            "type": "string"
          },
          "entity_id": {
            "type": "string"
          },
          "actor_id": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "changes": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "before": {},
                "after": {}
              }
            }
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "seq",
          "entity_type",
          "entity_id",
          "actor_id",
          "event",
          "changes",
          "created_at"
        ]
      },
      "HistoryPage": {
        "type": "object",
        "properties": {
          "history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HistoryEvent"
            }
          },
          "next_after": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "history",
          "next_after"
        ]
      },
      "CsvInput": {
        "type": "object",
        "properties": {
          "csv": {
            "type": "string",
            "description": "UTF-8 RFC 4180 CSV; at most one MiB and1000 data rows. Header title is required; optional project_id,text,notes,status,deadline,color,assignee_id. Validation and import recheck references and original owner quota."
          }
        },
        "required": [
          "csv"
        ]
      },
      "CsvValidation": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "row_count": {
            "type": "integer"
          },
          "valid_rows": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "field": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "errors_truncated": {
            "type": "boolean"
          },
          "quota_error": {
            "type": [
              "object",
              "null"
            ]
          },
          "active_tasks_added": {
            "type": "integer"
          },
          "payload_hash": {
            "type": "string"
          },
          "preview": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "valid",
          "row_count",
          "valid_rows",
          "errors",
          "errors_truncated",
          "quota_error",
          "active_tasks_added",
          "payload_hash",
          "preview"
        ]
      },
      "CsvImport": {
        "type": "object",
        "properties": {
          "import_id": {
            "type": "string"
          },
          "tree_id": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "tasks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "task_id": {
                  "type": "string"
                }
              },
              "required": [
                "row",
                "task_id"
              ]
            }
          },
          "payload_hash": {
            "type": "string"
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "import_id",
          "tree_id",
          "count",
          "tasks",
          "payload_hash",
          "replayed"
        ]
      },
      "OAuthError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "maxLength": 80
          },
          "error_description": {
            "type": "string",
            "maxLength": 2048
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "OAuthClient": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string",
            "maxLength": 160
          },
          "client_id_issued_at": {
            "type": "integer"
          },
          "client_name": {
            "type": "string",
            "maxLength": 120
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "string",
              "maxLength": 2048
            }
          },
          "token_endpoint_auth_method": {
            "const": "none"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "const": "code"
            }
          },
          "scope": {
            "type": "string",
            "maxLength": 100
          }
        },
        "required": [
          "client_id",
          "redirect_uris",
          "token_endpoint_auth_method"
        ],
        "additionalProperties": false
      },
      "OAuthRegistration": {
        "type": "object",
        "properties": {
          "client_name": {
            "type": "string",
            "maxLength": 120
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "maxLength": 2048
            }
          },
          "token_endpoint_auth_method": {
            "const": "none"
          },
          "grant_types": {
            "type": "array",
            "minItems": 1,
            "maxItems": 2,
            "uniqueItems": true,
            "items": {
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            }
          },
          "response_types": {
            "type": "array",
            "items": {
              "const": "code"
            }
          },
          "scope": {
            "type": "string",
            "maxLength": 100
          }
        },
        "required": [
          "redirect_uris"
        ],
        "additionalProperties": false
      },
      "OAuthToken": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "maxLength": 100
          },
          "token_type": {
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "maximum": 900
          },
          "refresh_token": {
            "type": "string",
            "maxLength": 100
          },
          "scope": {
            "type": "string",
            "maxLength": 100
          },
          "resource": {
            "type": "string",
            "maxLength": 2048
          }
        },
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "refresh_token",
          "scope",
          "resource"
        ],
        "additionalProperties": false
      },
      "OAuthConsent": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "maxLength": 64
          },
          "client": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "maxLength": 160
              },
              "name": {
                "type": "string",
                "maxLength": 120
              },
              "verified": {
                "const": false
              }
            },
            "required": [
              "id",
              "name",
              "verified"
            ],
            "additionalProperties": false
          },
          "redirect_uri": {
            "type": "string",
            "maxLength": 2048
          },
          "resource": {
            "type": "string",
            "maxLength": 2048
          },
          "scopes": {
            "type": "array",
            "items": {
              "enum": [
                "read",
                "write",
                "attachments"
              ]
            }
          },
          "expires_at": {
            "type": "integer"
          },
          "can_allow": {
            "type": "boolean"
          },
          "blocked_scopes": {
            "type": "array",
            "items": {
              "const": "write"
            }
          }
        },
        "required": [
          "request_id",
          "client",
          "redirect_uri",
          "resource",
          "scopes",
          "expires_at",
          "can_allow",
          "blocked_scopes"
        ],
        "additionalProperties": false
      },
      "OAuthGrant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 160
          },
          "client_id": {
            "type": "string",
            "maxLength": 160
          },
          "client_name": {
            "type": "string",
            "maxLength": 120
          },
          "resource": {
            "type": "string",
            "maxLength": 2048
          },
          "scopes": {
            "type": "array",
            "items": {
              "enum": [
                "read",
                "write",
                "attachments"
              ]
            }
          },
          "created_at": {
            "type": "integer"
          },
          "expires_at": {
            "type": "integer"
          },
          "revoked_at": {
            "type": [
              "integer",
              "null"
            ]
          },
          "token_revoked": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "client_id",
          "client_name",
          "resource",
          "scopes",
          "created_at",
          "expires_at"
        ],
        "additionalProperties": false
      },
      "TreeAppearanceInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "season": {
            "type": "string",
            "enum": [
              "default",
              "spring",
              "summer",
              "autumn",
              "winter"
            ]
          },
          "theme": {
            "type": "string",
            "enum": [
              "natural",
              "quiet",
              "bold"
            ]
          },
          "palette": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "wood",
                  "leaf",
                  "fruit"
                ],
                "additionalProperties": false,
                "properties": {
                  "wood": {
                    "type": "string",
                    "pattern": "^#[a-fA-F0-9]{6}$"
                  },
                  "leaf": {
                    "type": "string",
                    "pattern": "^#[a-fA-F0-9]{6}$"
                  },
                  "fruit": {
                    "type": "string",
                    "pattern": "^#[a-fA-F0-9]{6}$"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "fruits": {
            "type": "boolean"
          }
        }
      },
      "TreeArchiveTaskFields": {
        "type": "object",
        "properties": {
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Actual archive transition time; legacy unavailable dates remain null."
          }
        }
      },
      "GroveSeats": {
        "type": "object",
        "required": [
          "seats_verified",
          "quantity",
          "owner_included",
          "active",
          "occupied",
          "reserved",
          "available",
          "over_capacity",
          "sharing_available"
        ],
        "properties": {
          "seats_verified": {
            "type": "boolean"
          },
          "quantity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100
          },
          "subscription_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner_included": {
            "const": true
          },
          "active": {
            "type": "integer",
            "minimum": 0
          },
          "occupied": {
            "type": "integer",
            "minimum": 0
          },
          "reserved": {
            "type": "integer",
            "minimum": 0
          },
          "available": {
            "type": "integer",
            "minimum": 0
          },
          "over_capacity": {
            "type": "boolean"
          },
          "sharing_available": {
            "type": "boolean"
          }
        },
        "description": "Quantity is usable only after actual test-provider verification. Distinct people and pending recipients across all owned trees consume seats; the owner is included. Over capacity pauses all member grants and retains data."
      },
      "StudentRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "status",
          "version",
          "note",
          "created_at",
          "reviewed_at",
          "expires_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected",
              "revoked"
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 0
          },
          "note": {
            "type": "string",
            "maxLength": 500
          },
          "created_at": {
            "type": "integer"
          },
          "reviewed_at": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "StudentRequestResult": {
        "type": "object",
        "required": [
          "request"
        ],
        "properties": {
          "request": {
            "$ref": "#/components/schemas/StudentRequest"
          }
        }
      },
      "StudentPendingRequests": {
        "type": "object",
        "required": [
          "requests"
        ],
        "properties": {
          "requests": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "status",
                "version",
                "note",
                "created_at",
                "reviewed_at",
                "expires_at",
                "user_id"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "approved",
                    "rejected",
                    "revoked"
                  ]
                },
                "version": {
                  "type": "integer",
                  "minimum": 0
                },
                "note": {
                  "type": "string",
                  "maxLength": 500
                },
                "created_at": {
                  "type": "integer"
                },
                "reviewed_at": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "expires_at": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "user_id": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BillingQuote": {
        "type": "object",
        "required": [
          "mode",
          "verified",
          "plan",
          "interval",
          "billing_country",
          "currency",
          "region",
          "quantity",
          "unit_amount_minor",
          "subtotal_minor",
          "student_discount",
          "percent_off",
          "total_minor",
          "tax_included",
          "estimate",
          "student_version"
        ],
        "properties": {
          "mode": {
            "const": "test"
          },
          "verified": {
            "const": true
          },
          "plan": {
            "enum": [
              "sprout",
              "grove"
            ]
          },
          "interval": {
            "enum": [
              "month",
              "year"
            ]
          },
          "billing_country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          },
          "currency": {
            "enum": [
              "usd",
              "eur"
            ]
          },
          "region": {
            "enum": [
              "base",
              "cis"
            ]
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "unit_amount_minor": {
            "type": "integer",
            "minimum": 1
          },
          "subtotal_minor": {
            "type": "integer",
            "minimum": 1
          },
          "student_discount": {
            "type": "boolean"
          },
          "percent_off": {
            "enum": [
              0,
              50
            ]
          },
          "total_minor": {
            "type": "integer",
            "minimum": 1
          },
          "tax_included": {
            "const": false
          },
          "estimate": {
            "const": true
          },
          "student_version": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "BillingOffers": {
        "type": "object",
        "required": [
          "regional",
          "student",
          "promotions"
        ],
        "properties": {
          "regional": {
            "type": "object",
            "properties": {
              "configured": {
                "type": "boolean"
              },
              "verification_required": {
                "const": true
              },
              "countries": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[A-Z]{2}$"
                }
              }
            }
          },
          "student": {
            "type": "object",
            "properties": {
              "requests_enabled": {
                "type": "boolean"
              },
              "configured": {
                "type": "boolean"
              },
              "discount_available": {
                "const": false,
                "description": "A fresh quote verifies availability for a particular checkout; configuration alone is not proof."
              },
              "verification_required": {
                "const": true
              },
              "percent_off": {
                "const": 50
              },
              "eligible": {
                "type": "boolean"
              },
              "request": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/StudentRequest"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "promotions": {
            "type": "object",
            "properties": {
              "input_available": {
                "type": "boolean",
                "description": "Hosted Checkout can accept promotion-code input in configured test mode."
              },
              "codes_verified": {
                "const": false,
                "description": "No particular promotion code is asserted to exist."
              }
            }
          }
        }
      },
      "AdminConnectorFailures": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "available",
          "active",
          "total",
          "items",
          "truncated"
        ],
        "properties": {
          "available": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "total": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "items": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "source",
                "code",
                "count"
              ],
              "properties": {
                "source": {
                  "type": "string",
                  "enum": [
                    "webhook_connection",
                    "telegram_connection",
                    "webhook_delivery",
                    "telegram_update",
                    "telegram_notification"
                  ]
                },
                "code": {
                  "type": "string",
                  "maxLength": 64,
                  "description": "Existing administrative error-code allowlist, otherwise redacted_error. No raw provider content."
                },
                "count": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "truncated": {
            "type": "boolean"
          }
        }
      }
    }
  }
}
