{
  "openapi": "3.0.4",
  "info": {
    "title": "TaskPocket Public API",
    "description": "Manage tasks, projects and areas in a TaskPocket account.\n\nAuthenticate with a personal API token created in Settings → API, sent as `Authorization: Bearer tp_…`. Tokens carry `read` and/or `write` scopes; an endpoint you lack the scope for returns 403.\n\nTask and project notes are markdown on this API (they are stored as rich text internally); `descriptionHtml` exposes that rich text read-only, sanitised to the same safe subset.",
    "version": "v1"
  },
  "paths": {
    "/oauth/register": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "RFC 7591 dynamic client registration. Public clients only.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "tags": [
          "OAuth"
        ],
        "summary": "The authorization endpoint a client sends the browser to. Validates the request and hands\nthe user to the consent page; an untrusted redirect target gets an error page, never a\nredirect.",
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "response_mode",
            "in": "query",
            "description": "Only `query` is implemented. A request that asks for another mode is refused rather than silently answered in the query.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "RFC 6749 token endpoint: `authorization_code` and `refresh_token` grants.",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "grant_type": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string"
                  },
                  "refresh_token": {
                    "type": "string"
                  },
                  "resource": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string"
                  }
                }
              },
              "encoding": {
                "grant_type": {
                  "style": "form"
                },
                "code": {
                  "style": "form"
                },
                "redirect_uri": {
                  "style": "form"
                },
                "client_id": {
                  "style": "form"
                },
                "code_verifier": {
                  "style": "form"
                },
                "refresh_token": {
                  "style": "form"
                },
                "resource": {
                  "style": "form"
                },
                "scope": {
                  "style": "form"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "RFC 7009 revocation. Succeeds silently for unknown tokens.",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  }
                }
              },
              "encoding": {
                "token": {
                  "style": "form"
                },
                "token_type_hint": {
                  "style": "form"
                },
                "client_id": {
                  "style": "form"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/areas": {
      "get": {
        "tags": [
          "PublicAreas"
        ],
        "summary": "Lists the account's areas.",
        "parameters": [
          {
            "name": "Limit",
            "in": "query",
            "description": "Page size, 1–200. Out-of-range values are coerced rather than rejected: over the ceiling\nclamps down, and zero or negative means \"unspecified\", which takes the default.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "Cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAreaModelPublicPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "PublicAreas"
        ],
        "summary": "Creates an area. Free accounts are capped; see the 403 message for the limit.",
        "requestBody": {
          "description": "The area to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicAreaCreateModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicAreaCreateModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublicAreaCreateModel"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAreaModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/areas/{id}": {
      "get": {
        "tags": [
          "PublicAreas"
        ],
        "summary": "Fetches one area.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Area id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAreaModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "PublicAreas"
        ],
        "summary": "Renames an area.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Area id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Merge patch body; only `title` is accepted.",
          "content": {
            "application/json": {
              "schema": { }
            },
            "application/merge-patch+json": {
              "schema": { }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicAreaModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "delete": {
        "tags": [
          "PublicAreas"
        ],
        "summary": "Deletes an area. Its projects survive and become ungrouped.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Area id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/auth/token": {
      "post": {
        "tags": [
          "PublicAuth"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenExchangeRequest"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenExchangeRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/TokenExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/TokenExchangeResponse"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenExchangeResponse"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenExchangeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/me": {
      "get": {
        "tags": [
          "PublicMe"
        ],
        "summary": "Returns the account and the scopes granted to the presented token.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicIdentityModel"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects": {
      "get": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Lists projects, optionally filtered by area or lifecycle status.",
        "parameters": [
          {
            "name": "areaId",
            "in": "query",
            "description": "Restrict to projects in one area.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Restrict to active, completed or cancelled projects.",
            "schema": {
              "$ref": "#/components/schemas/PublicProjectStatus"
            }
          },
          {
            "name": "Limit",
            "in": "query",
            "description": "Page size, 1–200. Out-of-range values are coerced rather than rejected: over the ceiling\nclamps down, and zero or negative means \"unspecified\", which takes the default.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "Cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModelPublicPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Creates a project. Free accounts are capped; see the 403 message for the limit.",
        "requestBody": {
          "description": "The project to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCreateModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCreateModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCreateModel"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects/{id}": {
      "get": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Fetches one project with its sections. Tasks are fetched separately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Updates a project with a merge patch over title, description, areaId and deadlineDate.\nCompleted projects are not patchable.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Merge patch body.",
          "content": {
            "application/json": {
              "schema": { }
            },
            "application/merge-patch+json": {
              "schema": { }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "delete": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Moves a project to the Trash. Its open tasks go with it and report `inTrash` true;\ncompleted and cancelled tasks stay in the logbook. The user can restore the project from\nthe Trash page in the app. The API has no project restore, so treat the call as final.\nA project that is already in the Trash answers 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects/{id}/sections": {
      "get": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Lists a project's sections in display order.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicSectionModel"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects/{id}/tasks": {
      "get": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Convenience alias for `GET /tasks?projectId={id}`. Accepts the same view and status\nfilters, with the project fixed.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "view",
            "in": "query",
            "description": "Which named list to read. Defaults to every non-trashed task.",
            "schema": {
              "$ref": "#/components/schemas/PublicTaskView"
            }
          },
          {
            "name": "projectId",
            "in": "query",
            "description": "Restrict to one project. Combines with TaskPocket.Features.PublicApi.Models.PublicTaskListRequest.View.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "areaId",
            "in": "query",
            "description": "Restrict to projects in one area.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Restrict to open, completed or cancelled tasks.",
            "schema": {
              "$ref": "#/components/schemas/PublicTaskStatus"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Only tasks changed at or after this instant. The cheap way to poll for changes without a\nwebhook — and what an MCP server should use to stay in sync.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "Limit",
            "in": "query",
            "description": "Page size, 1–200. Out-of-range values are coerced rather than rejected: over the ceiling\nclamps down, and zero or negative means \"unspecified\", which takes the default.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "Cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModelPublicPage"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects/{id}/complete": {
      "post": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Completes a project, resolving its remaining open tasks.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "What to do with unfinished child tasks.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCompleteModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCompleteModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublicProjectCompleteModel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/projects/{id}/uncomplete": {
      "post": {
        "tags": [
          "PublicProjects"
        ],
        "summary": "Reopens a completed project.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Project id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicProjectModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks": {
      "get": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Lists tasks from a named view, with optional project, area, status and change-time filters.\nResults are paginated; follow `nextCursor` until it is null.",
        "parameters": [
          {
            "name": "view",
            "in": "query",
            "description": "Which named list to read. Defaults to every non-trashed task.",
            "schema": {
              "$ref": "#/components/schemas/PublicTaskView"
            }
          },
          {
            "name": "projectId",
            "in": "query",
            "description": "Restrict to one project. Combines with TaskPocket.Features.PublicApi.Models.PublicTaskListRequest.View.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "areaId",
            "in": "query",
            "description": "Restrict to projects in one area.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Restrict to open, completed or cancelled tasks.",
            "schema": {
              "$ref": "#/components/schemas/PublicTaskStatus"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Only tasks changed at or after this instant. The cheap way to poll for changes without a\nwebhook — and what an MCP server should use to stay in sync.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "Limit",
            "in": "query",
            "description": "Page size, 1–200. Out-of-range values are coerced rather than rejected: over the ceiling\nclamps down, and zero or negative means \"unspecified\", which takes the default.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "Cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModelPublicPage"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Creates a task.",
        "requestBody": {
          "description": "The task to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskCreateModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskCreateModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskCreateModel"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}": {
      "get": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Fetches a single task, including trashed ones.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "patch": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Updates a task with an RFC 7396 merge patch: omit a field to leave it alone, send null to\nclear it. Accepts title, description, dueDate, deadlineDate, reminderTime, recurrence and\nchecklist; any\nother field is rejected. Moving a task between lists or projects goes through\n`POST /tasks/{id}/move`, which also repositions it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Merge patch body.",
          "content": {
            "application/json": {
              "schema": { }
            },
            "application/merge-patch+json": {
              "schema": { }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      },
      "delete": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Moves a task to the trash. This is reversible via `POST /tasks/{id}/restore` — the\npublic API has no permanent delete, so an automated client cannot destroy data outright.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/move": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Moves a task to a different list or project, repositioning it at the top.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "Destination.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskMoveModel"
              }
            },
            "text/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskMoveModel"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/PublicTaskMoveModel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/complete": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Marks a task done. For a recurring task this rolls the series to its next occurrence and\nreturns that, matching what the app does.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/uncomplete": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Reopens a completed task.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/cancel": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Marks a task cancelled — resolved without being done.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/uncancel": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Reopens a cancelled task.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/api/public/v1/tasks/{id}/restore": {
      "post": {
        "tags": [
          "PublicTasks"
        ],
        "summary": "Restores a task from the trash.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Task id.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTaskModel"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired token."
          },
          "403": {
            "description": "The token lacks the required scope."
          }
        },
        "security": [
          {
            "ApiToken": [ ]
          }
        ]
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "WellKnown"
        ],
        "summary": "RFC 8414 authorization server metadata.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "WellKnown"
        ],
        "summary": "RFC 9728 protected resource metadata. It is served at three URLs:\nthe host root, which the 401 challenge points at;\nthe path-inserted form for the API identifier (§3.1), which path-aware clients try first;\nand the path-inserted form for the MCP server URL, which an MCP client derives from the\nURL it was given.\n§3.3 requires the `resource` value to equal the identifier the path was built from,\nso each path form echoes its own URL. Both values are under one audience (see\n`OAuthAuthorizationService.ResourceMatches`). Any other path is 404, never a\nlookalike document a cache could keep.",
        "parameters": [
          {
            "name": "resourcePath",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource/{resourcePath}": {
      "get": {
        "tags": [
          "WellKnown"
        ],
        "summary": "RFC 9728 protected resource metadata. It is served at three URLs:\nthe host root, which the 401 challenge points at;\nthe path-inserted form for the API identifier (§3.1), which path-aware clients try first;\nand the path-inserted form for the MCP server URL, which an MCP client derives from the\nURL it was given.\n§3.3 requires the `resource` value to equal the identifier the path was built from,\nso each path form echoes its own URL. Both values are under one audience (see\n`OAuthAuthorizationService.ResourceMatches`). Any other path is 404, never a\nlookalike document a cache could keep.",
        "parameters": [
          {
            "name": "resourcePath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtectedResourceMetadata"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AuthorizationServerMetadata": {
        "type": "object",
        "properties": {
          "issuer": {
            "type": "string",
            "nullable": true
          },
          "authorization_endpoint": {
            "type": "string",
            "nullable": true
          },
          "token_endpoint": {
            "type": "string",
            "nullable": true
          },
          "registration_endpoint": {
            "type": "string",
            "nullable": true
          },
          "revocation_endpoint": {
            "type": "string",
            "nullable": true
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "response_modes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Only the query mode is implemented; the RFC 8414 default would also claim fragment.",
            "nullable": true
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "revocation_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "service_documentation": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 8414 §2. Only the members that apply to this server."
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "properties": {
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "client_name": {
            "type": "string",
            "nullable": true
          },
          "client_uri": {
            "type": "string",
            "nullable": true
          },
          "logo_uri": {
            "type": "string",
            "nullable": true
          },
          "software_id": {
            "type": "string",
            "nullable": true
          },
          "software_version": {
            "type": "string",
            "nullable": true
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "nullable": true
          },
          "scope": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 7591 §2 registration request. Only the fields a public client can meaningfully set."
      },
      "ClientRegistrationResponse": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string",
            "nullable": true
          },
          "client_id_issued_at": {
            "type": "integer",
            "format": "int64"
          },
          "client_name": {
            "type": "string",
            "nullable": true
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "nullable": true
          },
          "scope": {
            "type": "string",
            "nullable": true
          },
          "client_uri": {
            "type": "string",
            "nullable": true
          },
          "logo_uri": {
            "type": "string",
            "nullable": true
          },
          "software_id": {
            "type": "string",
            "nullable": true
          },
          "software_version": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 7591 §3.2.1 registration response."
      },
      "OAuthErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "nullable": true
          },
          "error_description": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 6749 §5.2 error body."
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": { }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "properties": {
          "resource": {
            "type": "string",
            "nullable": true
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "resource_documentation": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 9728 §2."
      },
      "PublicAreaCreateModel": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 500,
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Body for `POST /api/public/v1/areas`."
      },
      "PublicAreaModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A top-level grouping of projects."
      },
      "PublicAreaModelPublicPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicAreaModel"
            },
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass back as `?cursor=` to fetch the next page. Null when this is the last page.\nTreat it as opaque — the encoding is not part of the contract.",
            "nullable": true
          },
          "hasMore": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false,
        "description": "A page of results. Cursor-based rather than offset-based because these lists mutate\nconstantly — an offset pager silently skips or repeats rows when a task is completed\nmid-pagination."
      },
      "PublicChecklistItemInput": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 500,
            "minLength": 1,
            "type": "string"
          },
          "completed": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PublicChecklistItemModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "completed": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "PublicIdentityModel": {
        "type": "object",
        "properties": {
          "accountId": {
            "type": "string",
            "format": "uuid"
          },
          "tokenId": {
            "type": "string",
            "format": "uuid"
          },
          "tokenName": {
            "type": "string",
            "description": "The label the user gave this token in Settings.",
            "nullable": true
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Effective scopes, after expanding any implications.",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Who the presented token belongs to and what it may do."
      },
      "PublicMonthlyMode": {
        "enum": [
          "dayOfMonth",
          "nthWeekday"
        ],
        "type": "string",
        "description": "Monthly only: how the day within each target month is chosen."
      },
      "PublicProjectCompleteModel": {
        "type": "object",
        "properties": {
          "completeChildTasks": {
            "type": "boolean",
            "description": "`true` marks remaining open tasks complete; `false` (the default) cancels them,\n            matching the app's default."
          }
        },
        "additionalProperties": false,
        "description": "What happens to a project's unfinished tasks when the project is completed. The app asks the\nuser; an API caller has to say up front."
      },
      "PublicProjectCreateModel": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 1024,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 100000,
            "type": "string",
            "description": "Project notes, as markdown.",
            "nullable": true
          },
          "areaId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "deadlineDate": {
            "type": "string",
            "description": "A hard deadline. Requires a Pro subscription.",
            "format": "date",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Body for `POST /api/public/v1/projects`."
      },
      "PublicProjectModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Project notes as markdown.",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/PublicProjectStatus"
          },
          "areaId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "areaTitle": {
            "type": "string",
            "description": "Denormalized so a client can render a grouped list without a second request.",
            "nullable": true
          },
          "deadlineDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicSectionModel"
            },
            "nullable": true
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A project as third parties see it. Tasks are fetched separately and paginated."
      },
      "PublicProjectModelPublicPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicProjectModel"
            },
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass back as `?cursor=` to fetch the next page. Null when this is the last page.\nTreat it as opaque — the encoding is not part of the contract.",
            "nullable": true
          },
          "hasMore": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false,
        "description": "A page of results. Cursor-based rather than offset-based because these lists mutate\nconstantly — an offset pager silently skips or repeats rows when a task is completed\nmid-pagination."
      },
      "PublicProjectStatus": {
        "enum": [
          "active",
          "completed",
          "cancelled"
        ],
        "type": "string",
        "description": "Lifecycle filter for project listings."
      },
      "PublicRecurrenceFrequency": {
        "enum": [
          "daily",
          "weekly",
          "monthly",
          "yearly"
        ],
        "type": "string"
      },
      "PublicRecurrenceModel": {
        "required": [
          "frequency"
        ],
        "type": "object",
        "properties": {
          "frequency": {
            "$ref": "#/components/schemas/PublicRecurrenceFrequency"
          },
          "interval": {
            "type": "integer",
            "description": "Repeat every N periods. 1–366.",
            "format": "int32"
          },
          "weekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicWeekday"
            },
            "description": "Weekly: which days to repeat on, e.g. `[\"monday\", \"thursday\"]`. Empty means \"the same\nweekday as the due date\". Also pairs with TaskPocket.Features.PublicApi.Models.PublicRecurrenceModel.WeekOfMonth for NthWeekday monthly rules.",
            "nullable": true
          },
          "fromCompletion": {
            "type": "boolean",
            "description": "False = the next date is measured from the due date (a strict schedule). True = measured from\nthe day the task is actually completed."
          },
          "monthlyMode": {
            "$ref": "#/components/schemas/PublicMonthlyMode"
          },
          "dayOfMonth": {
            "type": "integer",
            "description": "Monthly DayOfMonth target: 1–31, or -1 for the last day of the month.",
            "format": "int32",
            "nullable": true
          },
          "weekOfMonth": {
            "type": "integer",
            "description": "Monthly NthWeekday ordinal: 1–4 for first..fourth, or -1 for last.",
            "format": "int32",
            "nullable": true
          },
          "until": {
            "type": "string",
            "description": "Inclusive end date. The series stops once the next occurrence would fall after it.",
            "format": "date",
            "nullable": true
          },
          "count": {
            "type": "integer",
            "description": "Total occurrences including the current one. Null repeats indefinitely.",
            "format": "int32",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A repeat rule. Weekdays are a string list here rather than the internal bitmask: a bitmask is\nunusable from a script or an LLM without a lookup table, and gets silently mis-set."
      },
      "PublicSectionModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "A heading that groups tasks inside a project."
      },
      "PublicTaskCreateModel": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "maxLength": 1024,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 100000,
            "type": "string",
            "description": "Notes, as markdown. Converted to the app's rich-text format on the way in.",
            "nullable": true
          },
          "list": {
            "$ref": "#/components/schemas/PublicTaskList"
          },
          "projectId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sectionId": {
            "type": "string",
            "description": "Section within the project. Only valid when TaskPocket.Features.PublicApi.Models.PublicTaskCreateModel.List is `project`.",
            "format": "uuid",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "description": "When to work on it.",
            "format": "date",
            "nullable": true
          },
          "deadlineDate": {
            "type": "string",
            "description": "A hard deadline. Requires a Pro subscription; otherwise the request is refused.",
            "format": "date",
            "nullable": true
          },
          "reminderTime": {
            "type": "string",
            "description": "Reminder time of day on TaskPocket.Features.PublicApi.Models.PublicTaskCreateModel.DueDate, as \"HH:mm\". Needs a due date.",
            "format": "time",
            "nullable": true
          },
          "recurrence": {
            "$ref": "#/components/schemas/PublicRecurrenceModel"
          },
          "checklist": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicChecklistItemInput"
            },
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Body for `POST /api/public/v1/tasks`."
      },
      "PublicTaskList": {
        "enum": [
          "inbox",
          "project",
          "someday"
        ],
        "type": "string",
        "description": "Where a task lives. Mirrors TaskPocket.Entities.TaskLocation but serializes as a string —\nthe internal enum goes over the wire as an integer, which is unreadable in a public contract\nand impossible to extend without silently renumbering existing clients."
      },
      "PublicTaskModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Task notes as markdown. Null when the task has no notes.",
            "nullable": true
          },
          "descriptionHtml": {
            "type": "string",
            "description": "The stored rich-text HTML, for clients that need exact fidelity. Read-only.",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/PublicTaskStatus"
          },
          "list": {
            "$ref": "#/components/schemas/PublicTaskList"
          },
          "projectId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sectionId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "dueDate": {
            "type": "string",
            "description": "When the task is scheduled to be worked on.",
            "format": "date",
            "nullable": true
          },
          "deadlineDate": {
            "type": "string",
            "description": "A hard deadline. Setting this requires a Pro subscription.",
            "format": "date",
            "nullable": true
          },
          "reminderTime": {
            "type": "string",
            "description": "Time of day on TaskPocket.Features.PublicApi.Models.PublicTaskModel.DueDate to be reminded, as \"HH:mm\" in the account timezone.\nNull when there is no reminder. Requires a due date; clearing the date clears this too.",
            "format": "time",
            "nullable": true
          },
          "recurrence": {
            "$ref": "#/components/schemas/PublicRecurrenceModel"
          },
          "checklist": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicChecklistItemModel"
            },
            "nullable": true
          },
          "inTrash": {
            "type": "boolean",
            "description": "True when the task is in the trash. Trashed tasks keep their status."
          },
          "inLogbook": {
            "type": "boolean",
            "description": "True once a resolved task has moved to the Logbook."
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "description": "A task as third parties see it. Deliberately not `TaskResponse`: that model is a moving\ncontract shared with the web app and leaks ordering keys, `accountId` and integer enums.\nFields here change only with an API version bump."
      },
      "PublicTaskModelPublicPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicTaskModel"
            },
            "nullable": true
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass back as `?cursor=` to fetch the next page. Null when this is the last page.\nTreat it as opaque — the encoding is not part of the contract.",
            "nullable": true
          },
          "hasMore": {
            "type": "boolean",
            "readOnly": true
          }
        },
        "additionalProperties": false,
        "description": "A page of results. Cursor-based rather than offset-based because these lists mutate\nconstantly — an offset pager silently skips or repeats rows when a task is completed\nmid-pagination."
      },
      "PublicTaskMoveModel": {
        "required": [
          "list"
        ],
        "type": "object",
        "properties": {
          "list": {
            "$ref": "#/components/schemas/PublicTaskList"
          },
          "projectId": {
            "type": "string",
            "description": "Required when TaskPocket.Features.PublicApi.Models.PublicTaskMoveModel.List is `project`.",
            "format": "uuid",
            "nullable": true
          },
          "sectionId": {
            "type": "string",
            "description": "Optional section within the destination project.",
            "format": "uuid",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "Body for `POST /api/public/v1/tasks/{id}/move`. Moving is its own operation rather than a\npatch field because it also repositions the task within its destination list — a side effect a\ncaller should ask for explicitly, not get by editing one field."
      },
      "PublicTaskStatus": {
        "enum": [
          "open",
          "completed",
          "cancelled"
        ],
        "type": "string",
        "description": "A task's resolution state. Derived from CompletedAt/CancelledAt rather than making callers\ninterpret two nullable timestamps."
      },
      "PublicTaskView": {
        "enum": [
          "inbox",
          "today",
          "upcoming",
          "someday",
          "anytime",
          "logbook",
          "trash",
          "all"
        ],
        "type": "string",
        "description": "The named list a caller wants to read. One enum instead of six near-identical endpoints —\nwhich also makes this a single, unambiguous MCP tool parameter."
      },
      "PublicWeekday": {
        "enum": [
          "sunday",
          "monday",
          "tuesday",
          "wednesday",
          "thursday",
          "friday",
          "saturday"
        ],
        "type": "string",
        "description": "A day of the week. Deliberately not System.DayOfWeek: the BCL enum carries no\nstring converter, so it would only deserialize from an integer — and a public API that takes\n`weekdays: [1, 4]` is exactly the unusable bitmask-style contract this model exists to\navoid. Values match DayOfWeek's ordinals so the stored bitmask maths is unchanged."
      },
      "TokenExchangeRequest": {
        "required": [
          "code",
          "codeVerifier",
          "redirectUri"
        ],
        "type": "object",
        "properties": {
          "code": {
            "maxLength": 512,
            "minLength": 1,
            "type": "string"
          },
          "codeVerifier": {
            "maxLength": 256,
            "minLength": 1,
            "type": "string"
          },
          "redirectUri": {
            "maxLength": 512,
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Sent by the extension (anonymous) to redeem a one-time code for a long-lived API\ntoken. The PKCE TaskPocket.Features.Extension.Models.TokenExchangeRequest.CodeVerifier proves the caller started the flow."
      },
      "TokenExchangeResponse": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "The raw API token. Shown to the extension exactly once; never recoverable.",
            "nullable": true
          },
          "tokenType": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "nullable": true
          },
          "token_type": {
            "type": "string",
            "nullable": true
          },
          "expires_in": {
            "type": "integer",
            "format": "int32"
          },
          "refresh_token": {
            "type": "string",
            "nullable": true
          },
          "scope": {
            "type": "string",
            "nullable": true
          }
        },
        "additionalProperties": false,
        "description": "RFC 6749 §5.1 token response."
      }
    },
    "securitySchemes": {
      "ApiToken": {
        "type": "http",
        "description": "A personal API token from Settings → API. Format: `tp_…`",
        "scheme": "bearer"
      }
    }
  },
  "tags": [
    {
      "name": "OAuth",
      "description": "The OAuth 2.1 authorization server endpoints: registration, authorize, token, revoke.\n\nDeliberately NOT `[ApiController]`. That attribute short-circuits model-state failures\ninto RFC 7807 bodies, and OAuth clients parse `{ \"error\": \"…\" }` (RFC 6749 §5.2) instead.\nTaskPocket.Middleware.OAuthErrorResponseAttribute shapes every error for the same reason. It also means a `[FromForm]`\nparameter does not infer `multipart/form-data`, so the explicit\n`application/x-www-form-urlencoded` below is the only body type the token endpoint takes.\nRate limiting: `register` has its own budget (`oauth-register`). The rest carry no\ncredential to key on, so they sit under the global per-IP ceiling in Program.cs alone —\na per-bearer policy here would let a fabricated header open a fresh budget per request."
    },
    {
      "name": "PublicAreas",
      "description": "Areas: the top-level grouping projects belong to."
    },
    {
      "name": "PublicAuth",
      "description": "Public API token endpoint. Anonymous + PKCE: the extension redeems its one-time code\n(with the matching verifier) for a long-lived API token."
    },
    {
      "name": "PublicMe",
      "description": "Identity of the calling token. The natural first call for any integration: it confirms the\ntoken works and reports exactly which scopes it carries, so a client can fail loudly at\nstartup instead of at the first write it isn't allowed to make."
    },
    {
      "name": "PublicProjects",
      "description": "Projects, their sections, and the tasks inside them."
    },
    {
      "name": "PublicTasks",
      "description": "Tasks: the primary resource of the public API."
    },
    {
      "name": "WellKnown",
      "description": "Discovery documents. An MCP client reads these two and needs nothing else configured:\nthe resource document names the authorization server, and the server document names every\nendpoint and what it supports."
    }
  ]
}