{
  "openapi": "3.1.0",
  "info": {
    "title": "Konduit Public API",
    "version": "1.0.0",
    "description": "Konduit's public REST API for programmatic access to datatables, lists, tasks, comments, time entries, attachments, and forms. Authentication is via workspace-scoped Bearer tokens. CORS is enabled on every endpoint so browser-based integrations work without a server-side proxy.",
    "contact": {
      "name": "Konduit Support",
      "email": "team@konduit.work",
      "url": "https://docs.konduit.work"
    },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://api.konduit.work/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Workspace", "description": "Identity + member discovery" },
    { "name": "DataTables", "description": "Relational tables (clients, inventory, etc.)" },
    { "name": "Jobs", "description": "Async background jobs (e.g. bulk insert)" },
    { "name": "Spaces & Lists", "description": "Workspace structure" },
    { "name": "Tasks", "description": "Work items with custom fields, statuses, assignees" },
    { "name": "Comments", "description": "Task discussions" },
    { "name": "Time Entries", "description": "Manual time logging + running timers" },
    { "name": "Attachments", "description": "File uploads on tasks and datatable rows" },
    { "name": "Forms", "description": "Public intake forms (read-only API)" }
  ],

  "paths": {
    "/me": {
      "get": {
        "tags": ["Workspace"],
        "summary": "Identify the workspace + members visible to this API key",
        "operationId": "getMe",
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Identity" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PlanGated" }
        }
      }
    },

    "/datatables": {
      "get": {
        "tags": ["DataTables"],
        "summary": "List all datatables in the workspace",
        "operationId": "listDataTables",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": {
              "datatables": { "type": "array", "items": { "$ref": "#/components/schemas/DataTableSummary" } }
            } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },

    "/datatables/{tableId}/schema": {
      "get": {
        "tags": ["DataTables"],
        "summary": "Get a datatable's column schema",
        "operationId": "getTableSchema",
        "parameters": [{ "$ref": "#/components/parameters/TableId" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": {
              "columns": { "type": "array", "items": { "$ref": "#/components/schemas/Column" } }
            } } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/datatables/{tableId}/rows": {
      "get": {
        "tags": ["DataTables"],
        "summary": "List rows with pagination, filtering, and sorting",
        "operationId": "listRows",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 500 } },
          { "name": "sort", "in": "query", "description": "Multi-sort by column name(s): `Name:asc,Revenue:desc`", "schema": { "type": "string" } },
          { "name": "select", "in": "query", "description": "Projection by column name(s): `Name,Email,Revenue`", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "rows": { "type": "array", "items": { "$ref": "#/components/schemas/Row" } },
                "total": { "type": "integer" },
                "page": { "type": "integer" },
                "pageSize": { "type": "integer" }
              }
            } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "tags": ["DataTables"],
        "summary": "Insert one row",
        "operationId": "createRow",
        "parameters": [{ "$ref": "#/components/parameters/TableId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["data"],
          "properties": { "data": { "type": "object", "additionalProperties": true, "description": "Object keyed by column ID" } }
        }, "examples": {
          "default": { "value": { "data": { "col_09965ed24705": "Acme Corp", "col_9470fd97206c": "active", "col_38c7c1a8eeea": 50000 } } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Row" } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/datatables/{tableId}/rows/bulk": {
      "post": {
        "tags": ["DataTables"],
        "summary": "Insert multiple rows synchronously (≤250 rows/req)",
        "description": "For larger imports use the async [jobs/insert](#tag/Jobs/operation/createBulkInsertJob) endpoint instead.",
        "operationId": "bulkInsertRows",
        "parameters": [{ "$ref": "#/components/parameters/TableId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["rows"],
          "properties": { "rows": { "type": "array", "items": { "type": "object", "additionalProperties": true }, "maxItems": 250 } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "success": { "type": "boolean" },
              "count": { "type": "integer" },
              "total": { "type": "integer" }
            }
          } } } },
          "403": { "$ref": "#/components/responses/PlanGated" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },

    "/datatables/{tableId}/jobs/insert": {
      "post": {
        "tags": ["Jobs"],
        "summary": "Submit an async bulk-insert job (up to 50,000 rows)",
        "description": "Returns `202 Accepted` with a `job_id`. Poll [`GET /jobs/{jobId}`](#tag/Jobs/operation/getJob) for progress and final result. Plan-limited: Pro = 1 concurrent job, Business = 5.",
        "operationId": "createBulkInsertJob",
        "parameters": [{ "$ref": "#/components/parameters/TableId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["rows"],
          "properties": { "rows": { "type": "array", "items": { "type": "object", "additionalProperties": true }, "maxItems": 50000, "minItems": 1 } }
        } } } },
        "responses": {
          "202": { "description": "Job queued", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "job_id": { "type": "string" },
              "status": { "type": "string", "enum": ["queued"] },
              "rows_submitted": { "type": "integer" }
            }
          } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "403": { "$ref": "#/components/responses/PlanGated" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },

    "/jobs/{jobId}": {
      "get": {
        "tags": ["Jobs"],
        "summary": "Get the status of a background job",
        "description": "Workspace-scoped: a job belonging to another workspace returns 404 (not 403, to avoid leaking existence).",
        "operationId": "getJob",
        "parameters": [{ "name": "jobId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/datatables/{tableId}/rows/{rowId}": {
      "get": {
        "tags": ["DataTables"],
        "summary": "Get a single row",
        "operationId": "getRow",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "rowId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Row" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["DataTables"],
        "summary": "Partially update a row",
        "operationId": "updateRow",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "rowId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["data"],
          "properties": { "data": { "type": "object", "additionalProperties": true } }
        } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Row" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["DataTables"],
        "summary": "Permanently delete a row",
        "operationId": "deleteRow",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "rowId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Deleted" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/datatables/{tableId}/rows/{rowId}/attachments": {
      "post": {
        "tags": ["Attachments"],
        "summary": "Upload a file to an attachment column on a row",
        "operationId": "uploadRowAttachment",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "rowId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "col", "in": "query", "required": false, "description": "Attachment column ID (defaults to the first attachment column)", "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": {
          "type": "object",
          "required": ["file"],
          "properties": { "file": { "type": "string", "format": "binary" } }
        } } } },
        "responses": {
          "200": { "description": "Uploaded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RowAttachment" } } } },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" }
        }
      }
    },

    "/datatables/{tableId}/rows/{rowId}/attachments/{key}": {
      "delete": {
        "tags": ["Attachments"],
        "summary": "Delete a row attachment",
        "operationId": "deleteRowAttachment",
        "parameters": [
          { "$ref": "#/components/parameters/TableId" },
          { "name": "rowId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "key", "in": "path", "required": true, "description": "URL-encoded R2 key from the attachment record", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Deleted" },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },

    "/spaces": {
      "get": {
        "tags": ["Spaces & Lists"],
        "summary": "List non-private spaces with their lists",
        "operationId": "listSpaces",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "spaces": { "type": "array", "items": { "$ref": "#/components/schemas/Space" } } }
        } } } } }
      },
      "post": {
        "tags": ["Spaces & Lists"],
        "summary": "Create a space (auto-creates default statuses + a 'Tasks' list)",
        "operationId": "createSpace",
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["name"],
          "properties": {
            "name": { "type": "string" },
            "color": { "type": "string", "example": "#7c3aed" },
            "icon": { "type": "string", "example": "fa-solid fa-briefcase" }
          }
        } } } },
        "responses": {
          "200": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Space" } } } },
          "403": { "$ref": "#/components/responses/PlanGated" }
        }
      }
    },

    "/spaces/{spaceId}": {
      "patch": {
        "tags": ["Spaces & Lists"],
        "summary": "Update a space's name, color, or icon",
        "operationId": "updateSpace",
        "parameters": [{ "name": "spaceId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "color": { "type": "string" },
            "icon": { "type": "string" }
          }
        } } } },
        "responses": { "200": { "description": "Updated" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/spaces/{spaceId}/lists": {
      "post": {
        "tags": ["Spaces & Lists"],
        "summary": "Create a list under a space",
        "operationId": "createList",
        "parameters": [{ "name": "spaceId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["name"],
          "properties": {
            "name": { "type": "string" },
            "color": { "type": "string" },
            "icon": { "type": "string" }
          }
        } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } } },
          "403": { "$ref": "#/components/responses/PlanGated" }
        }
      }
    },

    "/lists": {
      "get": {
        "tags": ["Spaces & Lists"],
        "summary": "List all non-private lists in the workspace",
        "operationId": "listLists",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "lists": { "type": "array", "items": { "$ref": "#/components/schemas/List" } } }
        } } } } }
      }
    },

    "/lists/{listId}": {
      "get": {
        "tags": ["Spaces & Lists"],
        "summary": "Get a list",
        "operationId": "getList",
        "parameters": [{ "$ref": "#/components/parameters/ListId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } } }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "tags": ["Spaces & Lists"],
        "summary": "Update a list's name, color, or icon",
        "operationId": "updateList",
        "parameters": [{ "$ref": "#/components/parameters/ListId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "color": { "type": "string" },
            "icon": { "type": "string" }
          }
        } } } },
        "responses": { "200": { "description": "Updated" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/lists/{listId}/schema": {
      "get": {
        "tags": ["Spaces & Lists"],
        "summary": "Get a list's statuses and custom fields",
        "operationId": "getListSchema",
        "parameters": [{ "$ref": "#/components/parameters/ListId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "statuses": { "type": "array", "items": { "$ref": "#/components/schemas/Status" } },
            "fields": { "type": "array", "items": { "$ref": "#/components/schemas/CustomField" } }
          }
        } } } } }
      }
    },

    "/lists/{listId}/tasks": {
      "get": {
        "tags": ["Tasks"],
        "summary": "List tasks in a list",
        "operationId": "listTasks",
        "parameters": [
          { "$ref": "#/components/parameters/ListId" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "q", "in": "query", "description": "Title search", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "tasks": { "type": "array", "items": { "$ref": "#/components/schemas/Task" } },
            "limit": { "type": "integer" },
            "offset": { "type": "integer" }
          }
        } } } } }
      },
      "post": {
        "tags": ["Tasks"],
        "summary": "Create a task — JSON body OR multipart with attachments",
        "description": "Two body formats:\n\n1. `application/json` — task fields directly\n2. `multipart/form-data` — `data` field (JSON) + one or more `file` fields. **All-or-nothing**: if any attachment fails, the task is rolled back.",
        "operationId": "createTask",
        "parameters": [{ "$ref": "#/components/parameters/ListId" }],
        "requestBody": { "required": true, "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/TaskCreate" } },
          "multipart/form-data": { "schema": {
            "type": "object",
            "required": ["data"],
            "properties": {
              "data": { "type": "string", "description": "JSON-encoded TaskCreate" },
              "file": { "type": "array", "items": { "type": "string", "format": "binary" }, "maxItems": 10 }
            }
          } }
        } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "attachments": { "type": "array", "items": { "$ref": "#/components/schemas/Attachment" } }
            }
          } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" }
        }
      }
    },

    "/tasks/{taskId}": {
      "get": {
        "tags": ["Tasks"],
        "summary": "Get a task",
        "operationId": "getTask",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Task" } } } }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "tags": ["Tasks"],
        "summary": "Update a task (partial)",
        "operationId": "updateTask",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TaskPatch" } } } },
        "responses": { "200": { "description": "Updated" }, "400": { "$ref": "#/components/responses/ValidationError" }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "delete": {
        "tags": ["Tasks"],
        "summary": "Soft-delete a task (recoverable for 30 days)",
        "operationId": "deleteTask",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "responses": { "200": { "description": "Deleted" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/tasks/{taskId}/comments": {
      "get": {
        "tags": ["Comments"],
        "summary": "List comments on a task",
        "operationId": "listComments",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "comments": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } } }
        } } } } }
      },
      "post": {
        "tags": ["Comments"],
        "summary": "Post a comment",
        "operationId": "createComment",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["content"],
          "properties": {
            "content": { "oneOf": [{ "type": "string" }, { "type": "object", "description": "Tiptap JSON" }] },
            "comment_as": { "type": "string", "description": "Workspace member ID — post on behalf of this user (service account use case)" }
          }
        } } } },
        "responses": { "200": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" } } } } } } }
      }
    },

    "/tasks/{taskId}/comments/{commentId}": {
      "patch": {
        "tags": ["Comments"],
        "summary": "Edit a comment (master keys: any; scoped keys: own only)",
        "operationId": "updateComment",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "commentId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "required": ["content"],
          "properties": { "content": { "oneOf": [{ "type": "string" }, { "type": "object" }] } }
        } } } },
        "responses": { "200": { "description": "Updated" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "delete": {
        "tags": ["Comments"],
        "summary": "Delete a comment",
        "operationId": "deleteComment",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "commentId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Deleted" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/tasks/{taskId}/time-entries": {
      "get": {
        "tags": ["Time Entries"],
        "summary": "Read time logged on a task",
        "operationId": "listTimeEntries",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "users": { "type": "array", "items": { "type": "object" } },
            "total_seconds": { "type": "integer" },
            "entries": { "type": "array", "items": { "$ref": "#/components/schemas/TimeEntry" } }
          }
        } } } } }
      },
      "post": {
        "tags": ["Time Entries"],
        "summary": "Log time — manual entry, start timer, or stop timer",
        "operationId": "createTimeEntry",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "oneOf": [
            {
              "type": "object", "required": ["action"], "properties": {
                "action": { "type": "string", "enum": ["manual"] },
                "duration": { "type": "integer", "description": "Seconds" },
                "billable": { "type": "boolean" },
                "note": { "type": "string" },
                "date_start": { "type": "integer", "description": "Unix seconds" },
                "date_end": { "type": "integer" },
                "as_user": { "type": "string", "description": "Workspace member ID" }
              }
            },
            { "type": "object", "required": ["action"], "properties": { "action": { "type": "string", "enum": ["start"] }, "as_user": { "type": "string" } } },
            { "type": "object", "required": ["action"], "properties": { "action": { "type": "string", "enum": ["stop"] } } }
          ]
        } } } },
        "responses": { "200": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TimeEntry" } } } } }
      }
    },

    "/tasks/{taskId}/time-entries/{entryId}": {
      "patch": {
        "tags": ["Time Entries"],
        "summary": "Edit a stopped time entry",
        "operationId": "updateTimeEntry",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "entryId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "duration": { "type": "integer" },
            "note": { "type": "string" },
            "billable": { "type": "boolean" },
            "date_start": { "type": "integer" },
            "date_end": { "type": "integer" }
          }
        } } } },
        "responses": { "200": { "description": "Updated" } }
      },
      "delete": {
        "tags": ["Time Entries"],
        "summary": "Delete a time entry",
        "operationId": "deleteTimeEntry",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "entryId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Deleted" } }
      }
    },

    "/tasks/{taskId}/attachments": {
      "get": {
        "tags": ["Attachments"],
        "summary": "List attachments on a task",
        "operationId": "listTaskAttachments",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "attachments": { "type": "array", "items": { "$ref": "#/components/schemas/Attachment" } } }
        } } } } }
      },
      "post": {
        "tags": ["Attachments"],
        "summary": "Upload a file to an existing task",
        "operationId": "uploadTaskAttachment",
        "parameters": [{ "$ref": "#/components/parameters/TaskId" }],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": {
          "type": "object",
          "required": ["file"],
          "properties": { "file": { "type": "string", "format": "binary" } }
        } } } },
        "responses": {
          "201": { "description": "Uploaded", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": { "attachment": { "$ref": "#/components/schemas/Attachment" } }
          } } } },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" }
        }
      }
    },

    "/tasks/{taskId}/attachments/{attachmentId}": {
      "get": {
        "tags": ["Attachments"],
        "summary": "Download an attachment (binary stream)",
        "operationId": "downloadTaskAttachment",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "attachmentId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/octet-stream": {} } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Attachments"],
        "summary": "Delete an attachment",
        "operationId": "deleteTaskAttachment",
        "parameters": [
          { "$ref": "#/components/parameters/TaskId" },
          { "name": "attachmentId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Deleted" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/forms": {
      "get": {
        "tags": ["Forms"],
        "summary": "List forms",
        "operationId": "listForms",
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": { "forms": { "type": "array", "items": { "$ref": "#/components/schemas/Form" } } }
        } } } } }
      }
    },

    "/forms/{formId}": {
      "get": {
        "tags": ["Forms"],
        "summary": "Get a form's full definition",
        "operationId": "getForm",
        "parameters": [{ "name": "formId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Form" } } } }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },

    "/forms/{formId}/submissions": {
      "get": {
        "tags": ["Forms"],
        "summary": "List submissions for a form",
        "operationId": "listFormSubmissions",
        "parameters": [
          { "name": "formId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } },
          { "name": "since", "in": "query", "description": "Unix timestamp; only submissions after this", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "submissions": { "type": "array", "items": { "$ref": "#/components/schemas/FormSubmission" } },
            "limit": { "type": "integer" },
            "offset": { "type": "integer" }
          }
        } } } } }
      }
    }
  },

  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "kdt_<key>",
        "description": "Workspace-scoped API key created in **Workspace Settings → API Keys**. Format: `kdt_` followed by the random secret."
      }
    },
    "parameters": {
      "TableId": { "name": "tableId", "in": "path", "required": true, "schema": { "type": "string" } },
      "ListId":  { "name": "listId",  "in": "path", "required": true, "schema": { "type": "string" } },
      "TaskId":  { "name": "taskId",  "in": "path", "required": true, "schema": { "type": "string" } }
    },
    "responses": {
      "Unauthorized":     { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden":        { "description": "Insufficient scope or plan does not allow this action", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound":         { "description": "Resource not found in your workspace", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "ValidationError":  { "description": "Validation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "PayloadTooLarge":  { "description": "Request exceeds size or row-count limits", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited":      { "description": "Per-minute, per-month, or concurrent-job limit hit", "headers": { "Retry-After": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "PlanGated":        { "description": "Feature requires a higher plan", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" },
          "limitReached": { "type": "boolean", "description": "Set on plan-limit failures" },
          "resource": { "type": "string", "description": "Limit resource name when applicable" },
          "limit": { "type": "integer" },
          "current": { "type": "integer" },
          "transient": { "type": "boolean", "description": "Set on retryable infrastructure errors (503)" }
        },
        "required": ["error"]
      },
      "Identity": {
        "type": "object",
        "properties": {
          "workspace": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "plan": { "type": "string", "enum": ["free", "pro", "business", "scale"] }
            }
          },
          "members": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } }
        }
      },
      "DataTableSummary": {
        "type": "object",
        "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "row_count": { "type": "integer" } }
      },
      "Column": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "type": { "type": "string", "enum": ["text", "longtext", "email", "phone", "link", "number", "currency", "slider", "checkbox", "date", "dropdown", "tags", "people", "datatable", "formula", "autonumber", "attachment", "created_at", "updated_at"] },
          "options": { "type": "array", "items": { "type": "object" }, "description": "For dropdown / tags fields" },
          "currency_code": { "type": "string" },
          "format": { "type": "string", "description": "For autonumber" },
          "formula": { "type": "string" },
          "readonly": { "type": "boolean" }
        }
      },
      "Row": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "version": { "type": "integer" },
          "data": { "type": "object", "additionalProperties": true, "description": "Keyed by column ID" }
        }
      },
      "RowAttachment": {
        "type": "object",
        "properties": {
          "attachment": {
            "type": "object",
            "properties": {
              "key": { "type": "string" },
              "filename": { "type": "string" },
              "size": { "type": "integer" },
              "contentType": { "type": "string" },
              "uploadedAt": { "type": "integer" }
            }
          },
          "row": { "$ref": "#/components/schemas/Row" }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "enum": ["api_bulk_insert"] },
          "status": { "type": "string", "enum": ["queued", "processing", "done", "failed"] },
          "resource_id": { "type": "string", "description": "tableId for api_bulk_insert" },
          "created_at": { "type": "integer" },
          "updated_at": { "type": "integer" },
          "completed_at": { "type": "integer", "nullable": true },
          "error_message": { "type": "string", "nullable": true },
          "metadata": {
            "type": "object",
            "properties": {
              "table_name": { "type": "string" },
              "submitted": { "type": "integer", "description": "Initial submission count" },
              "row_count": { "type": "integer", "description": "Rows successfully inserted so far" },
              "progress": { "type": "number", "minimum": 0, "maximum": 1 },
              "total": { "type": "integer" },
              "errors": { "type": "integer", "description": "Count of chunks that errored" }
            }
          }
        }
      },
      "Space": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "color": { "type": "string" },
          "icon": { "type": "string" },
          "default_list_id": { "type": "string" },
          "lists": { "type": "array", "items": { "$ref": "#/components/schemas/List" } }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "space_id": { "type": "string" },
          "space_name": { "type": "string" },
          "folder_id": { "type": "string", "nullable": true },
          "color": { "type": "string", "nullable": true },
          "icon": { "type": "string", "nullable": true }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "color": { "type": "string" },
          "type": { "type": "string", "enum": ["active", "done", "archived"] }
        }
      },
      "CustomField": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "type": { "type": "string" },
          "options": { "type": "array" },
          "mode": { "type": "string", "enum": ["select", "items"], "description": "datatable-link mode" },
          "table_id": { "type": "string" },
          "readonly": { "type": "boolean" }
        }
      },
      "TaskCreate": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "status_id": { "type": "string", "description": "Defaults to first active status" },
          "priority": { "type": "string", "enum": ["low", "normal", "high", "urgent"], "nullable": true },
          "due_date": { "type": "integer", "description": "Unix seconds" },
          "date_start": { "type": "integer", "description": "Unix seconds" },
          "assignees": { "type": "array", "items": { "type": "string" }, "description": "Workspace member user IDs" },
          "custom_data": { "type": "object", "additionalProperties": true, "description": "Keyed by custom field ID" },
          "time_estimate": { "type": "integer", "description": "Minutes" },
          "project_id": { "type": "string" },
          "parent_task_id": { "type": "string" }
        }
      },
      "TaskPatch": {
        "allOf": [{ "$ref": "#/components/schemas/TaskCreate" }, { "type": "object", "properties": { "description": { "type": "string", "description": "Plain text or Tiptap JSON" }, "repeat_config": { "anyOf": [{ "$ref": "#/components/schemas/RepeatConfig" }, { "type": "null" }], "description": "Recurrence config. Pass null to clear." }, "type_id": { "type": "string" } } }]
      },
      "RepeatConfig": {
        "type": "object",
        "description": "Per-task recurrence. When trigger=on_schedule, the task clones forward on the given cadence. When trigger=on_complete, the task clones only after the current occurrence is marked done.",
        "required": ["trigger"],
        "properties": {
          "trigger": {
            "type": "string",
            "enum": ["on_schedule", "on_complete"],
            "description": "on_schedule = fixed cadence. on_complete = clone after the user marks done."
          },
          "frequency": {
            "type": "string",
            "enum": ["daily", "weekdays", "weekly", "monthly"],
            "description": "Required when trigger=on_schedule. Ignored for on_complete."
          },
          "day_of_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6,
            "description": "0=Sunday..6=Saturday. Required when frequency=weekly."
          },
          "day_of_month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 31,
            "description": "1..31. Required when frequency=monthly. Clamped to actual month length at run time."
          },
          "time": {
            "type": "string",
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "description": "HH:MM 24-hour, in workspace timezone. Defaults to 09:00."
          },
          "end_date": {
            "type": "integer",
            "description": "Unix timestamp; recurrence stops after this moment."
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "status_id": { "type": "string" },
          "priority": { "type": "integer" },
          "due_date": { "type": "integer", "nullable": true },
          "date_start": { "type": "integer", "nullable": true },
          "custom_data": { "type": "object", "additionalProperties": true },
          "assignees": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } } } },
          "created_at": { "type": "integer" },
          "updated_at": { "type": "integer" }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "task_id": { "type": "string" },
          "author_id": { "type": "string" },
          "author_name": { "type": "string" },
          "content": { "oneOf": [{ "type": "string" }, { "type": "object" }] },
          "content_type": { "type": "string", "enum": ["text", "tiptap"] },
          "created_at": { "type": "integer" },
          "updated_at": { "type": "integer", "nullable": true }
        }
      },
      "TimeEntry": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "user_id": { "type": "string" },
          "duration": { "type": "integer", "description": "Seconds" },
          "billable": { "type": "integer", "enum": [0, 1] },
          "note": { "type": "string" },
          "is_running": { "type": "integer", "enum": [0, 1] },
          "date_start": { "type": "integer" },
          "date_end": { "type": "integer", "nullable": true }
        }
      },
      "Attachment": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "filename": { "type": "string" },
          "mime_type": { "type": "string" },
          "size_bytes": { "type": "integer" },
          "url": { "type": "string", "description": "Authenticated download URL — same Bearer token works" }
        }
      },
      "Form": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "target_type": { "type": "string", "enum": ["task", "datatable_row"] },
          "target_id": { "type": "string" },
          "is_published": { "type": "integer", "enum": [0, 1] },
          "submission_count": { "type": "integer" },
          "field_config": { "type": "array" },
          "rules": { "type": "object" },
          "settings": { "type": "object" },
          "created_at": { "type": "integer" },
          "updated_at": { "type": "integer" }
        }
      },
      "FormSubmission": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "data": { "type": "object", "additionalProperties": true },
          "created_entity_type": { "type": "string", "enum": ["task", "datatable_row"] },
          "created_entity_id": { "type": "string" },
          "created_at": { "type": "integer" }
        }
      }
    }
  }
}
