{
  "openapi": "3.1.0",
  "info": {
    "title": "TaskOH",
    "version": "1.0.0",
    "description": "Read and manage TaskOH tasks as the owner of the personal access token. Tasks live in projects; statuses are inbox, planned, in_progress, in_review, needs_client and done. Every write is recorded in the TaskOH activity log as 'via API'.",
    "contact": {
      "url": "https://taskoh.app"
    }
  },
  "servers": [
    {
      "url": "https://api.taskoh.app"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal access token created on the TaskOH Integrations page. Starts with toh_pat_."
      }
    },
    "schemas": {
      "Task": {
        "type": "object",
        "description": "A TaskOH task. label_ids, subtask_count and the assignee_* keys are present on single-task and project-list responses; project_name is present on the my-tasks response.",
        "properties": {
          "id": { "type": "integer" },
          "project_id": { "type": "integer" },
          "parent_id": { "type": ["integer", "null"], "description": "Parent task id when this is a subtask." },
          "title": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "details": { "type": ["string", "null"] },
          "status": { "type": "string", "enum": ["inbox", "planned", "in_progress", "in_review", "needs_client", "done"] },
          "type": { "type": "string", "enum": ["request", "bug", "task"] },
          "scope": { "type": "string", "enum": ["unclassified", "in_scope", "out_of_scope"] },
          "is_billable": { "type": "boolean" },
          "required_minutes": { "type": ["integer", "null"] },
          "fee_amount": { "type": ["string", "null"], "description": "Decimal string with two fractional digits, e.g. \"250.50\"." },
          "fee_currency": { "type": ["string", "null"], "description": "Three-letter uppercase currency code." },
          "billing_client_visible": { "type": "boolean" },
          "client_visible": { "type": "boolean" },
          "created_by_user_id": { "type": ["integer", "null"] },
          "assignee_user_id": { "type": ["integer", "null"] },
          "priority": { "type": ["integer", "null"], "description": "1 low, 2 medium, 3 high; null means none." },
          "due_date": { "type": ["string", "null"], "description": "YYYY-MM-DD" },
          "metadata": { "type": "object" },
          "position": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "label_ids": { "type": "array", "items": { "type": "integer" } },
          "subtask_count": { "type": "integer" },
          "assignee_name": { "type": ["string", "null"] },
          "assignee_email": { "type": ["string", "null"] },
          "assignee_last_activity_at": { "type": ["string", "null"] },
          "project_name": { "type": ["string", "null"] }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "workspace_id": { "type": "integer" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "client_portal_slug": { "type": ["string", "null"] },
          "settings": { "type": "object" },
          "created_by": { "type": ["integer", "null"] },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "my_role": { "type": "string", "enum": ["admin", "project_manager", "guest"], "description": "The token owner's role in this project." }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "task_id": { "type": "integer" },
          "author_user_id": { "type": "integer" },
          "author_name": { "type": ["string", "null"] },
          "author_email": { "type": ["string", "null"] },
          "author_last_activity_at": { "type": ["string", "null"] },
          "body": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "const": false },
          "error": {
            "type": "object",
            "properties": {
              "type": { "type": "string" },
              "message": { "type": "string" },
              "code": { "type": "integer" }
            }
          }
        }
      },
      "TaskWrite": {
        "type": "object",
        "description": "Fields accepted when creating or updating a task. On update, omitted fields stay unchanged; send null to clear a nullable field.",
        "properties": {
          "title": { "type": "string", "maxLength": 255 },
          "description": { "type": ["string", "null"] },
          "details": { "type": ["string", "null"], "description": "Longer notes; update only." },
          "status": { "type": "string", "enum": ["inbox", "planned", "in_progress", "in_review", "needs_client", "done"] },
          "type": { "type": "string", "enum": ["request", "bug", "task"] },
          "priority": { "type": ["integer", "null"], "minimum": 1, "maximum": 3, "description": "1 low, 2 medium, 3 high; null clears." },
          "due_date": { "type": ["string", "null"], "description": "YYYY-MM-DD; null clears." },
          "assignee_user_id": { "type": ["integer", "null"], "description": "User id of a project member; null unassigns." },
          "parent_id": { "type": ["integer", "null"], "description": "Make this task a subtask of the given task in the same project; null promotes it to top level." },
          "client_visible": { "type": "boolean", "description": "Whether the client can see the task in the portal. Default false." },
          "position": { "type": "integer", "minimum": 0, "maximum": 100000 },
          "scope": { "type": "string", "enum": ["unclassified", "in_scope", "out_of_scope"] },
          "is_billable": { "type": "boolean", "description": "Only allowed when scope is out_of_scope." },
          "required_minutes": { "type": ["integer", "null"], "description": "Positive multiple of 15." },
          "fee_amount": { "type": ["string", "null"], "description": "Positive decimal string, e.g. \"250.50\"; requires fee_currency." },
          "fee_currency": { "type": ["string", "null"], "pattern": "^[A-Z]{3}$" },
          "billing_client_visible": { "type": "boolean", "description": "Share hours and fee with the client. Requires the project team role." }
        }
      }
    }
  },
  "paths": {
    "/api/projects": {
      "get": {
        "operationId": "list_projects",
        "summary": "List my projects",
        "description": "Returns every project the token owner belongs to, with the owner's role in each. Use the project id for the other operations.",
        "x-openai-isConsequential": false,
        "responses": {
          "200": {
            "description": "Projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": {
                      "type": "object",
                      "properties": {
                        "count": { "type": "integer" },
                        "projects": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } },
                        "workspace_role": { "type": ["string", "null"], "enum": ["owner", "admin", "member", null] }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/dashboard/my-tasks": {
      "get": {
        "operationId": "list_my_tasks",
        "summary": "List my open tasks",
        "description": "Returns open tasks (status not done) assigned to the token owner across all projects, overdue first, then by priority and due date. Each task carries project_name.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of tasks to return (1-100, default 50; out-of-range values are clamped).",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of tasks with project_name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Task" } }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/projects/{id}/tasks": {
      "get": {
        "operationId": "list_project_tasks",
        "summary": "Search tasks in a project",
        "description": "Returns a page of tasks in one project, filtered by text, status, type, assignee, labels, priority, scope and billing. Filters in one group are OR-ed, groups are AND-ed. Guests only see client-visible tasks.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id from list_projects.",
            "schema": { "type": "integer" }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive text matched against title and description.",
            "schema": { "type": "string" }
          },
          {
            "name": "status[]",
            "in": "query",
            "required": false,
            "description": "Only tasks in these statuses. Repeat the parameter for several values.",
            "schema": { "type": "array", "items": { "type": "string", "enum": ["inbox", "planned", "in_progress", "in_review", "needs_client", "done"] } }
          },
          {
            "name": "type[]",
            "in": "query",
            "required": false,
            "description": "Only tasks of these types.",
            "schema": { "type": "array", "items": { "type": "string", "enum": ["request", "bug", "task"] } }
          },
          {
            "name": "assignee_user_id[]",
            "in": "query",
            "required": false,
            "description": "Only tasks assigned to these user ids.",
            "schema": { "type": "array", "items": { "type": "integer" } }
          },
          {
            "name": "label_ids[]",
            "in": "query",
            "required": false,
            "description": "Only tasks carrying these label ids.",
            "schema": { "type": "array", "items": { "type": "integer" } }
          },
          {
            "name": "priority[]",
            "in": "query",
            "required": false,
            "description": "Only tasks with these priorities: 1 low, 2 medium, 3 high.",
            "schema": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 3 } }
          },
          {
            "name": "scope[]",
            "in": "query",
            "required": false,
            "description": "Only tasks with these scope classifications.",
            "schema": { "type": "array", "items": { "type": "string", "enum": ["unclassified", "in_scope", "out_of_scope"] } }
          },
          {
            "name": "is_billable[]",
            "in": "query",
            "required": false,
            "description": "Only billable (1) or non-billable (0) tasks.",
            "schema": { "type": "array", "items": { "type": "string", "enum": ["1", "0"] } }
          },
          {
            "name": "parent_id",
            "in": "query",
            "required": false,
            "description": "Only subtasks of this task id. Omit to list top-level tasks and subtasks together.",
            "schema": { "type": "integer" }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key. Default position.",
            "schema": { "type": "string", "enum": ["position", "priority", "due_date", "created_at", "title", "status", "type", "scope", "is_billable", "assignee", "client_visible"], "default": "position" }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction. Default asc.",
            "schema": { "type": "string", "enum": ["asc", "desc"], "default": "asc" }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number, starting at 1.",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Tasks per page (1-100, default 50; out-of-range values are clamped). Keep at 50 or below so the response stays small enough for the assistant.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of tasks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": {
                      "type": "object",
                      "properties": {
                        "project_id": { "type": "integer" },
                        "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" } },
                        "total": { "type": "integer" },
                        "page": { "type": "integer" },
                        "per_page": { "type": "integer" },
                        "total_pages": { "type": "integer" }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Project not found or the token owner is not a member",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      },
      "post": {
        "operationId": "create_task",
        "summary": "Create a task in a project",
        "description": "Creates a task in the given project as the token owner. Only title is required; status defaults to inbox and type to task. Set parent_id to create a subtask.",
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project id from list_projects.",
            "schema": { "type": "integer" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  { "$ref": "#/components/schemas/TaskWrite" },
                  { "type": "object", "required": ["title"] }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": { "$ref": "#/components/schemas/Task" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "403": {
            "description": "Scope or billing fields sent by a guest",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Project not found or the token owner may not create tasks in it",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "Validation error; error.message says which field",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/tasks/{id}": {
      "get": {
        "operationId": "get_task",
        "summary": "Get one task",
        "description": "Returns one task with label_ids, subtask_count and assignee details. Guests receive only client-visible tasks; anything else answers 404.",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Task id.",
            "schema": { "type": "integer" }
          }
        ],
        "responses": {
          "200": {
            "description": "Task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": { "$ref": "#/components/schemas/Task" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Task not found or not accessible",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      },
      "patch": {
        "operationId": "update_task",
        "summary": "Update fields of a task",
        "description": "Updates the given fields of a task; omitted fields are unchanged. Use it to change status, assignee, dates, priority, type, visibility, scope and billing. Requires the project team role.",
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Task id.",
            "schema": { "type": "integer" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TaskWrite" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": { "$ref": "#/components/schemas/Task" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": {
            "description": "Task not found or the token owner may not edit it",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "Validation error; error.message says which field",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/tasks/{taskId}/comments": {
      "post": {
        "operationId": "add_comment",
        "summary": "Add a comment to a task",
        "description": "Posts a plain-text comment on a task as the token owner. Mention a project member as @their.email@example.com to notify them.",
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "description": "Task id.",
            "schema": { "type": "integer" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["body"],
                "properties": {
                  "body": { "type": "string", "minLength": 1, "description": "Comment text." }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created comment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "const": true },
                    "data": { "$ref": "#/components/schemas/Comment" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, expired or revoked token",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "403": {
            "description": "The token owner may not comment in this project",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": {
            "description": "Task not found or empty body",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  }
}
