{"openapi":"3.1.0","info":{"title":"Zoho Projects Gateway","version":"1.0.0","description":"Consumer-agnostic API for Zoho Projects. Zoho Projects is the system of record for sprints and tasks. Callers present a per-user gateway API key (from /wizard) or the operator GATEWAY_API_KEY. Reads go to Zoho immediately. Writes are a propose/approve queue, unless a per-user key sends the chat confirmation marker. This service uses its own Zoho OAuth client, separate from any other Zoho integrations."},"servers":[{"url":"https://zoho-gateway.staging.runit-solutions.com","description":"RunIT k3s (zoho-gateway.runit-solutions.com)"},{"url":"http://0.0.0.0:43147","description":"Local"}],"security":[{"gatewayApiKey":[]}],"tags":[{"name":"meta"},{"name":"projects"},{"name":"tasks"},{"name":"issues"},{"name":"search"},{"name":"writes"}],"paths":{"/health":{"get":{"tags":["meta"],"security":[],"summary":"Liveness","operationId":"health","responses":{"200":{"description":"OK"}}}},"/openapi.json":{"get":{"tags":["meta"],"security":[],"summary":"This OpenAPI document","operationId":"openapi","responses":{"200":{"description":"OpenAPI 3.1 JSON"}}}},"/meta":{"get":{"tags":["meta"],"summary":"Module map and write-gate status","operationId":"meta","responses":{"200":{"description":"Catalog"}}}},"/usage":{"get":{"tags":["meta"],"summary":"Zoho call count for the current UTC day","operationId":"usage","responses":{"200":{"description":"Usage"}}}},"/portals":{"get":{"tags":["projects"],"summary":"List portals","operationId":"listPortals","responses":{"200":{"description":"Portals"}}}},"/projects":{"get":{"tags":["projects"],"summary":"List projects (or search with q)","operationId":"listProjects","parameters":[{"name":"page","in":"query","schema":{"type":"integer"}},{"name":"per_page","in":"query","schema":{"type":"integer"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","description":"If set, uses Zoho Search API (module=projects)","schema":{"type":"string"}}],"responses":{"200":{"description":"Projects"}}}},"/projects/{projectId}":{"get":{"tags":["projects"],"summary":"Get project","operationId":"getProject","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Project"}}}},"/projects/{projectId}/tasks":{"get":{"tags":["tasks"],"summary":"List tasks (or search with q)","operationId":"listTasks","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer"}},{"name":"per_page","in":"query","schema":{"type":"integer"}},{"name":"q","in":"query","schema":{"type":"string"}},{"name":"sort_by","in":"query","schema":{"type":"string"}},{"name":"filter","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Tasks"}}}},"/projects/{projectId}/tasks/{taskId}":{"get":{"tags":["tasks"],"summary":"Get task","operationId":"getTask","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Task"}}}},"/projects/{projectId}/issues":{"get":{"tags":["issues"],"summary":"List issues (or search with q)","operationId":"listIssues","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer"}},{"name":"per_page","in":"query","schema":{"type":"integer"}},{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Issues"}}}},"/projects/{projectId}/issues/{issueId}":{"get":{"tags":["issues"],"summary":"Get issue","operationId":"getIssue","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"issueId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Issue"}}}},"/search":{"get":{"tags":["search"],"summary":"Portal or project search","operationId":"search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"module","in":"query","schema":{"type":"string"}},{"name":"project_id","in":"query","schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer"}},{"name":"per_page","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Search hits"}}}},"/writes":{"get":{"tags":["writes"],"summary":"List this API key's write proposals. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"listWrites","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","executing","unknown","approved","rejected","failed"]}}],"responses":{"200":{"description":"Write queue"},"403":{"description":"Writes disabled"}}}},"/writes/{id}":{"get":{"tags":["writes"],"summary":"Get one write proposal","operationId":"getWrite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Write proposal"},"404":{"description":"Not found for this API key"}}}},"/writes/approve-all":{"post":{"tags":["writes"],"summary":"Human on-command: approve pending writes serially (delay between Zoho calls). Requires confirm=true. Not for agents.","operationId":"batchApproveWrites","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirm"],"properties":{"confirm":{"type":"boolean"},"ids":{"type":"array","items":{"type":"string"},"description":"Optional subset. Default: all pending for this key."}}}}}},"responses":{"200":{"description":"Per-item summary (continues after failures)"},"400":{"description":"confirm=true missing"}}}},"/writes/reject-all":{"post":{"tags":["writes"],"summary":"Human on-command: reject pending writes. Requires confirm=true. Not for agents.","operationId":"batchRejectWrites","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirm"],"properties":{"confirm":{"type":"boolean"},"ids":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Per-item summary"},"400":{"description":"confirm=true missing"}}}},"/writes/{id}/approve":{"post":{"tags":["writes"],"summary":"Human on-command: send a pending write to Zoho. Requires confirm=true. Not for agents.","operationId":"approveWrite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirm"],"properties":{"confirm":{"type":"boolean","description":"Must be true. Human on-command only; agents must not set this."}}}}}},"responses":{"200":{"description":"Approved and sent to Zoho"},"400":{"description":"confirm=true missing"},"409":{"description":"Not pending"}}}},"/writes/{id}/resolve":{"post":{"tags":["writes"],"summary":"Human on-command: record the outcome of an unknown stuck write. Does not call Zoho. applied = already in Zoho; not_applied = not in Zoho. Requires confirm=true. Not for agents.","operationId":"resolveUnknownWrite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirm","outcome"],"properties":{"confirm":{"type":"boolean","description":"Must be true. Human on-command only; agents must not set this."},"outcome":{"type":"string","enum":["applied","not_applied"]}}}}}},"responses":{"200":{"description":"Recorded. Zoho was not called."},"400":{"description":"confirm or outcome missing"},"409":{"description":"Not unknown"}}}},"/writes/{id}/reject":{"post":{"tags":["writes"],"summary":"Human on-command: reject a pending write. Requires confirm=true. Not for agents.","operationId":"rejectWrite","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirm"],"properties":{"confirm":{"type":"boolean","description":"Must be true. Human on-command only; agents must not set this."}}}}}},"responses":{"200":{"description":"Rejected"},"400":{"description":"confirm=true missing"}}}},"/writes/projects/{projectId}/tasks":{"post":{"tags":["writes"],"summary":"Propose creating a task (does not call Zoho). Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"createTask","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dry_run":{"type":"boolean","description":"If true, preview only — nothing is stored or sent to Zoho."},"confirm":{"type":"boolean","description":"Alone, this does not execute. With approved_via=chat it is the chat marker."},"approved_via":{"type":"string","enum":["chat"]},"approved_by":{"type":"string"},"idempotency_key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"status":{"type":"object"}}}}}},"responses":{"200":{"description":"Dry-run preview"},"201":{"description":"Pending write stored"},"403":{"description":"Writes disabled"}}}},"/writes/projects/{projectId}/tasks/{taskId}":{"patch":{"tags":["writes"],"summary":"Propose updating a task (does not call Zoho). Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"updateTask","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dry_run":{"type":"boolean"},"name":{"type":"string"},"status":{"type":"object"}}}}}},"responses":{"200":{"description":"Pending write stored, chat execution, or dry-run"},"403":{"description":"Writes disabled"}}}},"/writes/projects/{projectId}/tasks/{taskId}/comments":{"post":{"tags":["writes"],"summary":"Create a task comment. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"createTaskComment","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dry_run":{"type":"boolean"},"confirm":{"type":"boolean"},"approved_via":{"type":"string","enum":["chat"]},"approved_by":{"type":"string"},"idempotency_key":{"type":"string"},"comment":{"type":"string"},"attachments":{"type":"array","items":{}},"thirdparty_id":{"type":"array","items":{}}}}}}},"responses":{"200":{"description":"Dry-run, or idempotent replay"},"201":{"description":"Pending write stored, or chat execution sent to Zoho"},"403":{"description":"Writes disabled, or operator key used the chat marker"}}}},"/writes/projects/{projectId}/tasks/{taskId}/comments/{commentId}":{"patch":{"tags":["writes"],"summary":"Update a task comment. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"updateTaskComment","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pending, dry-run, or chat execution"},"201":{"description":"Chat execution"}}},"delete":{"tags":["writes"],"summary":"Delete a task comment. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"deleteTaskComment","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pending, dry-run, or replay"},"201":{"description":"Chat execution"}}}},"/writes/projects/{projectId}/tasks/{taskId}/logs":{"post":{"tags":["writes"],"summary":"Create a task time log. Zoho POST /api/v3/.../log with module.type=task. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"createTaskTimeLog","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["date","bill_status"],"properties":{"dry_run":{"type":"boolean"},"confirm":{"type":"boolean"},"approved_via":{"type":"string","enum":["chat"]},"approved_by":{"type":"string"},"idempotency_key":{"type":"string"},"date":{"type":"string","description":"YYYY-MM-DD"},"bill_status":{"type":"string","enum":["Billable","Non Billable"]},"hours":{"type":"string"},"notes":{"type":"string"},"log_name":{"type":"string"},"start_time":{"type":"string"},"end_time":{"type":"string"}}}}}},"responses":{"200":{"description":"Dry-run, or idempotent replay"},"201":{"description":"Pending write stored, or chat execution sent to Zoho"}}}},"/writes/projects/{projectId}/tasks/{taskId}/logs/{logId}":{"patch":{"tags":["writes"],"summary":"Update a task time log. Zoho PATCH /api/v3/.../logs/{logId}. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"updateTaskTimeLog","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}},{"name":"logId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pending, dry-run, or replay"},"201":{"description":"Chat execution"}}},"delete":{"tags":["writes"],"summary":"Delete a task time log. Zoho DELETE /api/v3.1/.../timelogs/{logId}?module=task. Write routes are on (ZOHO_GATEWAY_WRITES=1). Without the chat marker, POST/PATCH/DELETE only propose a pending mutation. Zoho is called after a human approves in /wizard or POST /writes/{id}/approve with confirm=true. A per-user API key may execute immediately when the body also has confirm=true, approved_via=chat, approved_by, and idempotency_key.","operationId":"deleteTaskTimeLog","parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string"}},{"name":"logId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pending, dry-run, or replay"},"201":{"description":"Chat execution"}}}}},"components":{"securitySchemes":{"gatewayApiKey":{"type":"http","scheme":"bearer","bearerFormat":"API key","description":"Per-user key from /wizard, or the operator GATEWAY_API_KEY. Same header shape either way."}}}}