Zoho Projects

v1.0.0

Zoho Projects API integration with managed OAuth. Manage projects, tasks, milestones, tasklists, and team collaboration. Use this skill when users want to ma...

0· 355·0 current·0 all-time
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description (Zoho Projects with managed OAuth) matches what the SKILL.md instructs: calls to gateway.maton.ai and ctrl.maton.ai to proxy Zoho API calls and manage OAuth connections. The single required env var (MATON_API_KEY) is appropriate for a gateway-proxied integration.
Instruction Scope
Instructions are scoped to calling Maton endpoints (gateway.maton.ai and ctrl.maton.ai) and managing connections. They do not read arbitrary local files or unrelated environment variables. Important caveat: API requests and OAuth tokens are proxied through maton.ai — the skill will cause your Zoho project data and OAuth tokens to transit through that third-party service.
Install Mechanism
Instruction-only skill with no install spec and no code files; nothing is written to disk during install.
Credentials
Only one env var (MATON_API_KEY) is required, which is proportionate for a gateway-managed OAuth integration. Minor metadata inconsistency: the registry lists no primary credential even though MATON_API_KEY is required, but this is a bookkeeping issue rather than a functional mismatch.
Persistence & Privilege
always:false and no install actions or config writes; the skill does not request elevated persistence or modify other skills. Model invocation is allowed (platform default).
Assessment
This skill proxies Zoho API calls and OAuth via maton.ai and requires a MATON_API_KEY — installing it gives Maton the ability to access your Zoho connections on your behalf. Before installing: (1) confirm you trust maton.ai (review its privacy/security docs and OAuth scope handling), (2) use a dedicated least-privilege API key/account if possible, (3) verify OAuth scopes requested during connection flows, and (4) if you want to limit autonomous behavior, be aware the skill can be invoked by the agent (platform default) and restrict or review usage accordingly. Also note the small metadata inconsistency (no declared primary credential) but this does not affect functionality.

Like a lobster shell, security has layers — review code before you run it.

Runtime requirements

EnvMATON_API_KEY
latestvk97f9f2rfvkcy3xksdjvt7zt8s8218d8
355downloads
0stars
1versions
Updated 1mo ago
v1.0.0
MIT-0

Zoho Projects

Access the Zoho Projects API with managed OAuth authentication. Manage projects, tasks, milestones, tasklists, and team collaboration.

Quick Start

# List all portals
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-projects/restapi/portals/')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

Base URL

https://gateway.maton.ai/zoho-projects/{native-api-path}

Replace {native-api-path} with the actual Zoho Projects API endpoint path. The gateway proxies requests to projectsapi.zoho.com and automatically injects your OAuth token.

Authentication

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"

Getting Your API Key

  1. Sign in or create an account at maton.ai
  2. Go to maton.ai/settings
  3. Copy your API key

Connection Management

Manage your Zoho Projects OAuth connections at https://ctrl.maton.ai.

List Connections

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.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

Create Connection

python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'zoho-projects'}).encode()
req = urllib.request.Request('https://ctrl.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

Get Connection

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.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": "522c11a9-b879-4504-b267-355e3dbac99f",
    "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.

Delete Connection

python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.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

Specifying Connection

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://gateway.maton.ai/zoho-projects/restapi/portals/')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '522c11a9-b879-4504-b267-355e3dbac99f')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF

If omitted, the gateway uses the default (oldest) active connection.

API Reference

Portals

List Portals

GET /zoho-projects/restapi/portals/

Response:

{
  "portals": [
    {
      "id": 916020774,
      "id_string": "916020774",
      "name": "nunchidotapp",
      "plan": "Ultimate",
      "role": "admin",
      "project_count": {"active": 1}
    }
  ]
}

Get Portal Details

GET /zoho-projects/restapi/portal/{portal_id}/

Projects

List Projects

GET /zoho-projects/restapi/portal/{portal_id}/projects/

Query parameters: index, range, status, sort_column, sort_order

Response:

{
  "projects": [
    {
      "id": 2644874000000089119,
      "name": "My Project",
      "status": "active",
      "owner_name": "Byungkyu Park",
      "task_count": {"open": 3, "closed": 0},
      "project_percent": "0"
    }
  ]
}

Get Project Details

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/

Create Project

POST /zoho-projects/restapi/portal/{portal_id}/projects/
Content-Type: application/x-www-form-urlencoded

name=New+Project&owner={user_id}&description=Project+description

Required: name Optional: owner, description, start_date, end_date, template_id, group_id

Response:

{
  "projects": [
    {
      "id": 2644874000000096003,
      "name": "New Project",
      "status": "active"
    }
  ]
}

Update Project

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/
Content-Type: application/x-www-form-urlencoded

name=Updated+Name&status=active

Delete Project

DELETE /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/

Response:

{"response": "Project Trashed successfully"}

Tasks

List Tasks

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/

Query parameters: index, range, owner, status, priority, tasklist_id, sort_column

Response:

{
  "tasks": [
    {
      "id": 2644874000000089255,
      "name": "Task 3",
      "status": {"name": "Open", "type": "open"},
      "priority": "None",
      "completed": false,
      "tasklist": {"name": "General", "id": "2644874000000089245"}
    }
  ]
}

Get Task Details

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/

Get My Tasks

GET /zoho-projects/restapi/portal/{portal_id}/mytasks/

Query parameters: index, range, owner, status, priority, projects_ids

Create Task

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/
Content-Type: application/x-www-form-urlencoded

name=New+Task&tasklist_id={tasklist_id}&priority=High

Required: name Optional: person_responsible, tasklist_id, start_date, end_date, priority, description

Response:

{
  "tasks": [
    {
      "id": 2644874000000094001,
      "key": "EZ1-T4",
      "name": "New Task",
      "status": {"name": "Open", "type": "open"}
    }
  ]
}

Update Task

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/
Content-Type: application/x-www-form-urlencoded

name=Updated+Name&priority=High&percent_complete=50

Delete Task

DELETE /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/

Response:

{"response": "Task Trashed successfully"}

Task Comments

List Comments

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments/

Add Comment

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments/
Content-Type: application/x-www-form-urlencoded

content=This+is+a+comment

Response:

{
  "comments": [
    {
      "id": 2644874000000094015,
      "content": "This is a comment",
      "added_person": "Byungkyu Park"
    }
  ]
}

Update Comment

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments/{comment_id}/
Content-Type: application/x-www-form-urlencoded

content=Updated+comment

Delete Comment

DELETE /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/{task_id}/comments/{comment_id}/

Tasklists

List Tasklists

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasklists/

Query parameters: index, range, flag, milestone_id, sort_column

Response:

{
  "tasklists": [
    {
      "id": 2644874000000089245,
      "name": "General",
      "flag": "internal",
      "is_default": true,
      "task_count": {"open": 3}
    }
  ]
}

Create Tasklist

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasklists/
Content-Type: application/x-www-form-urlencoded

name=New+Tasklist&flag=internal

Required: name Optional: milestone_id, flag

Update Tasklist

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasklists/{tasklist_id}/
Content-Type: application/x-www-form-urlencoded

name=Updated+Name&milestone_id={milestone_id}

Delete Tasklist

DELETE /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasklists/{tasklist_id}/

Response:

{"response": "Tasklist Trashed successfully"}

Milestones

List Milestones

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/milestones/

Query parameters: index, range, status, flag, sort_column

Note: Returns 204 No Content if no milestones exist.

Create Milestone

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/milestones/
Content-Type: application/x-www-form-urlencoded

name=Phase+1&start_date=03-01-2026&end_date=03-15-2026&owner={user_id}&flag=internal

Required: name, start_date, end_date, owner, flag

Response:

{
  "milestones": [
    {
      "id": 2644874000000096133,
      "name": "Phase 1",
      "start_date": "03-01-2026",
      "end_date": "03-15-2026",
      "status": "notcompleted"
    }
  ]
}

Update Milestone

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/milestones/{milestone_id}/
Content-Type: application/x-www-form-urlencoded

name=Updated+Phase&start_date=03-01-2026&end_date=03-20-2026&owner={user_id}&flag=internal

Update Milestone Status

POST /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/milestones/{milestone_id}/status/
Content-Type: application/x-www-form-urlencoded

status=2

Status values: 1 = not completed, 2 = completed

Delete Milestone

DELETE /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/milestones/{milestone_id}/

Response:

{"response": "Milestone Trashed successfully"}

Users

List Users

GET /zoho-projects/restapi/portal/{portal_id}/users/

Response:

{
  "users": [
    {
      "id": "801698114",
      "zpuid": "2644874000000085003",
      "name": "Byungkyu Park",
      "email": "byungkyu@example.com",
      "role_name": "Administrator",
      "active": true
    }
  ]
}

Project Groups

List Groups

GET /zoho-projects/restapi/portal/{portal_id}/projects/groups

Response:

{
  "groups": [
    {
      "id": "2644874000000018001",
      "name": "Ungrouped Projects",
      "is_default": "true"
    }
  ]
}

Create Group

POST /zoho-projects/restapi/portal/{portal_id}/projectgroups
Content-Type: application/x-www-form-urlencoded

name=New+Group

Pagination

Use index and range parameters for pagination:

GET /zoho-projects/restapi/portal/{portal_id}/projects/{project_id}/tasks/?index=1&range=50
  • index: Starting record number (1-based)
  • range: Number of records to return

Code Examples

JavaScript

// List tasks in a project
const response = await fetch(
  'https://gateway.maton.ai/zoho-projects/restapi/portal/916020774/projects/2644874000000089119/tasks/',
  {
    headers: {
      'Authorization': `Bearer ${process.env.MATON_API_KEY}`
    }
  }
);
const data = await response.json();
console.log(data.tasks);

Python

import os
import requests

# Create a task
response = requests.post(
    'https://gateway.maton.ai/zoho-projects/restapi/portal/916020774/projects/2644874000000089119/tasks/',
    headers={
        'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    data={'name': 'New Task', 'priority': 'High'}
)
task = response.json()
print(task['tasks'][0]['id'])

Notes

  • All POST requests for create/update use application/x-www-form-urlencoded content type, not JSON
  • Portal ID is required for most endpoints and can be obtained from GET /restapi/portals/
  • Date format is MM-dd-yyyy (e.g., 03-01-2026)
  • Milestone creation requires all fields: name, start_date, end_date, owner, flag
  • Empty list responses return HTTP 204 No Content
  • Deleted items are moved to trash, not permanently deleted

Error Handling

StatusMeaning
204No content (empty list)
400Missing/invalid input parameter
401Invalid or missing Maton API key
404Resource not found
429Rate limited (100 requests per 2 minutes)
4xx/5xxPassthrough error from Zoho Projects API

Common error codes:

  • 6831: Input Parameter Missing
  • 6832: Input Parameter Does not Match the Pattern Specified

Troubleshooting: API Key Issues

  1. Check that the MATON_API_KEY environment variable is set:
echo $MATON_API_KEY
  1. Verify the API key is valid by listing connections:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.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

Troubleshooting: Invalid App Name

Ensure your URL path starts with zoho-projects. For example:

  • Correct: https://gateway.maton.ai/zoho-projects/restapi/portals/
  • Incorrect: https://gateway.maton.ai/restapi/portals/

Resources

Comments

Loading comments...