Access 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/共 3 个版本