Install
openclaw skills install zoho-projectsZoho Projects API V3 integration with managed OAuth. Manage projects, tasks, milestones, tasklists, and team collaboration. Use this skill when users want to manage project tasks, track time, organize milestones, or collaborate on projects. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Security: The MATON_API_KEY authenticates with Maton.ai but grants NO access to Zoho Projects by itself. Zoho access requires explicit OAuth authorization by the user through Maton's connect flow. Access is strictly scoped to the Zoho Projects account the user has authorized. Requires network access and valid Maton API key.
openclaw skills install zoho-projectsAccess the Zoho Projects API V3 with managed OAuth authentication. Manage projects, tasks, milestones, tasklists, and team collaboration.
# List all portals
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-projects/api/v3/portals')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/zoho-projects/api/v3/{endpoint}
The gateway proxies requests to projectsapi.zoho.com and automatically injects your OAuth token.
Important:
/api/v3/ prefix (not /restapi/)Content-Type: application/json)All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Zoho Projects OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=zoho-projects&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'zoho-projects'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2026-02-28T00:12:25.223434Z",
"last_updated_time": "2026-02-28T00:16:32.882675Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "zoho-projects",
"metadata": {},
"method": "OAUTH2"
}
}
Open the returned url in a browser to complete OAuth authorization.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Zoho Projects connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-projects/api/v3/portals')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
GET /zoho-projects/api/v3/portals
Response:
[
{
"id": "916020774",
"portal_name": "mycompany",
"org_name": "mycompany",
"timezone": "PST",
"project_plan": "Free",
"owner": {
"zpuid": "2644874000000085003",
"name": "John Doe",
"email": "john@example.com"
},
"profile": {
"name": "Portal Owner",
"id": 2644874000000085084
}
}
]
GET /zoho-projects/api/v3/portal/{portal_id}/projects
Query parameters: page, per_page, status (active, archived, template)
Response:
[
{
"id": "2644874000000089119",
"key": "NU-1",
"name": "My Project",
"project_type": "active",
"description": "Project description",
"owner": {
"zpuid": "2644874000000085003",
"name": "John Doe",
"email": "john@example.com"
},
"is_public_project": false,
"created_time": "2026-02-27T10:20:22.421Z",
"modified_time": "2026-02-27T10:20:22.421Z"
}
]
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}
POST /zoho-projects/api/v3/portal/{portal_id}/projects
Content-Type: application/json
{
"name": "New Project",
"description": "Project description"
}
Response (201):
{
"id": "2644874000000096003",
"key": "NU-2",
"name": "New Project",
"project_type": "active",
"description": "Project description",
"owner": {
"zpuid": "2644874000000085003",
"name": "John Doe"
},
"created_time": "2026-05-17T22:08:52.537Z"
}
PATCH /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}
Content-Type: application/json
{
"name": "Updated Name",
"description": "Updated description"
}
DELETE /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}
Returns 204 No Content on success.
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks
Query parameters: page, per_page, owner, status, priority, tasklist_id, sort_by
Response:
{
"page_info": {
"page": 1,
"per_page": 100,
"page_count": 3,
"has_next_page": false
},
"tasks": [
{
"id": "2644874000000089247",
"prefix": "EZ1-T1",
"name": "Task 1",
"status": {
"id": "2644874000000016068",
"name": "Open",
"is_closed_type": false
},
"priority": "none",
"project": {
"id": "2644874000000089119",
"name": "My Project"
},
"tasklist": {
"id": "2644874000000089245",
"name": "General"
},
"milestone": {
"id": "2644874000000000073",
"name": "None"
}
}
]
}
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}
POST /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks
Content-Type: application/json
{
"name": "New Task",
"priority": "high",
"description": "Task description",
"tasklist_id": "{tasklist_id}"
}
Optional fields: person_responsible, tasklist_id, start_date, end_date, priority, description
Response (201): Returns the created task object.
PATCH /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}
Content-Type: application/json
{
"name": "Updated Task Name",
"priority": "medium"
}
DELETE /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}
Returns 204 No Content on success.
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments
Response:
{
"page_info": {
"per_page": 100,
"has_next_page": false,
"count": 1,
"page": 1
},
"comments": [
{
"id": "2644874000000094015",
"comment": "This is a comment",
"created_time": "2026-05-17T22:08:51.264Z",
"created_by": {
"zpuid": "2644874000000085003",
"name": "John Doe"
}
}
]
}
POST /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments
Content-Type: application/json
{
"comment": "This is a comment"
}
Note: The field name is comment, not content.
Response (201): Returns the created comment object.
DELETE /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments/{comment_id}
Returns 204 No Content on success.
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasklists
Response:
{
"page_info": {
"page": 1,
"per_page": 200,
"page_count": 1,
"has_next_page": false
},
"tasklists": [
{
"id": "2644874000000089245",
"name": "General",
"flag": "internal",
"status": "active",
"milestone": {
"id": "2644874000000000073",
"name": "None"
},
"created_time": "2026-02-27T10:20:24.426Z"
}
]
}
POST /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasklists
Content-Type: application/json
{
"name": "New Tasklist",
"flag": "internal"
}
Optional fields: milestone_id, flag (internal or external)
Response (201): Returns the created tasklist object.
PATCH /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasklists/{tasklist_id}
Content-Type: application/json
{
"name": "Updated Tasklist Name"
}
DELETE /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasklists/{tasklist_id}
Returns 204 No Content on success.
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/milestones
Response:
{
"page_info": [
{
"per_page": 100,
"has_next_page": false,
"page": 1
}
],
"milestones": [
{
"id": "2644874000000096133",
"name": "Phase 1",
"start_date": "2026-05-17",
"end_date": "2026-06-01",
"flag": "internal",
"owner": {
"zpuid": "2644874000000085003",
"name": "John Doe"
},
"created_time": "2026-05-17T22:09:13.771Z"
}
]
}
POST /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/milestones
Content-Type: application/json
{
"name": "Phase 1",
"start_date": "06-01-2026",
"end_date": "06-15-2026",
"flag": "internal",
"owner_zpuid": "{user_zpuid}"
}
Required fields: name, start_date, end_date, flag, owner_zpuid
Note: Date format for creating milestones is MM-dd-yyyy.
Response (201): Returns the created milestone object.
PATCH /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/milestones/{milestone_id}
Content-Type: application/json
{
"name": "Updated Phase",
"end_date": "06-20-2026"
}
DELETE /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/milestones/{milestone_id}
Returns 204 No Content on success.
GET /zoho-projects/api/v3/portal/{portal_id}/users
Response:
{
"page_info": {
"per_page": 100,
"has_next_page": false,
"count": 1,
"page": 1
},
"users": [
{
"zpuid": "2644874000000085003",
"name": "John Doe",
"email": "john@example.com",
"is_active": true,
"role": {
"name": "Administrator",
"id": "2644874000000085005"
},
"added_time": "2026-02-27T10:19:11.719Z"
}
]
}
V3 uses page-based pagination with page and per_page parameters:
GET /zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks?page=1&per_page=50
Response includes page_info:
{
"page_info": {
"page": 1,
"per_page": 50,
"page_count": 25,
"has_next_page": true
},
"tasks": [...]
}
When has_next_page is true, increment page to get the next batch.
// List tasks in a project
const response = await fetch(
'https://api.maton.ai/zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
console.log(data.tasks);
import os
import requests
# Create a task
response = requests.post(
'https://api.maton.ai/zoho-projects/api/v3/portal/{portal_id}/projects/{project_id}/tasks',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json={'name': 'New Task', 'priority': 'high'}
)
task = response.json()
print(task['id'])
/api/v3/ prefix — do NOT use trailing slashesapplication/json content type (not form-urlencoded like V2)GET /api/v3/portalsMM-dd-yyyy (e.g., 06-01-2026)page + per_page (not index + range like V2)| Status | Meaning |
|---|---|
| 201 | Resource created successfully |
| 204 | Success with no content (delete operations) |
| 400 | Missing/invalid input parameter or invalid URL |
| 401 | Invalid or missing API key, or invalid OAuth scope |
| 404 | Resource not found |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Zoho Projects API |
V3 error format:
{
"error": {
"status_code": "400",
"title": "LESS_THAN_MIN_OCCURANCE",
"error_type": "FIELDS_VALIDATION_ERROR",
"details": [
{
"message": "Input Parameter Missing",
"field_name": "comment"
}
]
}
}
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Ensure your URL path starts with zoho-projects. For example:
https://api.maton.ai/zoho-projects/api/v3/portalshttps://api.maton.ai/api/v3/portalsV3 does NOT allow trailing slashes. For example:
https://api.maton.ai/zoho-projects/api/v3/portal/{portal_id}/projectshttps://api.maton.ai/zoho-projects/api/v3/portal/{portal_id}/projects/