Zoho Calendar
Access the Zoho Calendar API with managed OAuth authentication. Manage calendars and events with full CRUD operations, including recurring events and attendee management.
Quick Start
# List calendars
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars')
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-calendar/api/v1/{endpoint}
The gateway proxies requests to calendar.zoho.com/api/v1 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
- Sign in or create an account at maton.ai
- Go to maton.ai/settings
- Copy your API key
Connection Management
Manage your Zoho Calendar 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-calendar&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-calendar'}).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:
json
{
"connection": {
"connection_id": "21fd90f9-5935-43cd-b6c8-bde9d915ca80",
"status": "ACTIVE",
"creation_time": "2025-12-08T07:20:53.488460Z",
"last_updated_time": "2026-01-31T20:03:32.593153Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "zoho-calendar",
"metadata": {}
}
}
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 Calendar 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-calendar/api/v1/calendars')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '21fd90f9-5935-43cd-b6c8-bde9d915ca80')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If omitted, the gateway uses the default (oldest) active connection.
API Reference
Calendars
List Calendars
GET /zoho-calendar/api/v1/calendars
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
json
{
"calendars": [
{
"uid": "fda9b0b4ad834257b622cb3dc3555727",
"name": "My Calendar",
"color": "#8cbf40",
"textcolor": "#FFFFFF",
"timezone": "PST",
"isdefault": true,
"category": "own",
"privilege": "owner"
}
]
}
Get Calendar Details
GET /zoho-calendar/api/v1/calendars/{calendar_uid}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Calendar
POST /zoho-calendar/api/v1/calendars?calendarData={json}
Required Fields:
- name - Calendar name (max 50 characters)
- color - Hex color code (e.g., #FF5733)
Optional Fields:
- textcolor - Text color hex code
- description - Calendar description (max 1000 characters)
- timezone - Calendar timezone
- include_infreebusy - Show as Busy/Free (boolean)
- public - Visibility level (disable, freebusy, or view)
Example:
python <<'EOF'
import urllib.request, os, json, urllib.parse
calendarData = {
"name": "Work Calendar",
"color": "#FF5733",
"textcolor": "#FFFFFF",
"description": "My work calendar"
}
url = f'https://gateway.maton.ai/zoho-calendar/api/v1/calendars?calendarData={urllib.parse.quote(json.dumps(calendarData))}'
req = urllib.request.Request(url, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
json
{
"calendars": [
{
"uid": "86fb9745076e4672ae4324f05e1f5393",
"name": "Work Calendar",
"color": "#FF5733",
"textcolor": "#FFFFFF"
}
]
}
Delete Calendar
DELETE /zoho-calendar/api/v1/calendars/{calendar_uid}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars/86fb9745076e4672ae4324f05e1f5393', 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
Response:
json
{
"calendars": [
{
"uid": "86fb9745076e4672ae4324f05e1f5393",
"calstatus": "deleted"
}
]
}
Events
List Events
GET /zoho-calendar/api/v1/calendars/{calendar_uid}/events?range={json}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
range |
JSON object | Required. Start and end dates in format {"start":"yyyyMMdd","end":"yyyyMMdd"}. Max 31-day span. |
byinstance |
boolean | If true, recurring event instances are returned separately |
timezone |
string | Timezone for datetime values |
Example:
python <<'EOF'
import urllib.request, os, json, urllib.parse
from datetime import datetime, timedelta
today = datetime.now()
end_date = today + timedelta(days=7)
range_param = json.dumps({
"start": today.strftime("%Y%m%d"),
"end": end_date.strftime("%Y%m%d")
})
url = f'https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727/events?range={urllib.parse.quote(range_param)}'
req = urllib.request.Request(url)
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
json
{
"events": [
{
"uid": "[email protected]",
"title": "Team Meeting",
"dateandtime": {
"timezone": "America/Los_Angeles",
"start": "20260206T100000-0800",
"end": "20260206T110000-0800"
},
"isallday": false,
"etag": "1770368451507",
"organizer": "[email protected]"
}
]
}
Get Event Details
GET /zoho-calendar/api/v1/calendars/{calendar_uid}/events/{event_uid}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727/events/[email protected]')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Event
POST /zoho-calendar/api/v1/calendars/{calendar_uid}/events?eventdata={json}
Required Fields (in eventdata):
- dateandtime - Object with start, end, and optionally timezone
- Format: yyyyMMdd'T'HHmmss'Z' (GMT) for timed events
- Format: yyyyMMdd for all-day events
Optional Fields:
- title - Event name
- description - Event details (max 10,000 characters)
- location - Event location (max 255 characters)
- isallday - Boolean for all-day events
- isprivate - Boolean to hide details from non-delegates
- color - Hex color code
- attendees - Array of attendee objects
- reminders - Array of reminder objects
- rrule - Recurrence rule string (e.g., FREQ=DAILY;COUNT=5)
Example:
python <<'EOF'
import urllib.request, os, json, urllib.parse
from datetime import datetime, timedelta
start_time = datetime.utcnow() + timedelta(hours=1)
end_time = start_time + timedelta(hours=1)
eventdata = {
"title": "Team Meeting",
"dateandtime": {
"timezone": "America/Los_Angeles",
"start": start_time.strftime("%Y%m%dT%H%M%SZ"),
"end": end_time.strftime("%Y%m%dT%H%M%SZ")
},
"description": "Weekly team sync",
"location": "Conference Room A"
}
url = f'https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727/events?eventdata={urllib.parse.quote(json.dumps(eventdata))}'
req = urllib.request.Request(url, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
json
{
"events": [
{
"uid": "[email protected]",
"title": "Team Meeting",
"dateandtime": {
"timezone": "America/Los_Angeles",
"start": "20260206T100000-0800",
"end": "20260206T110000-0800"
},
"etag": "1770368451507",
"estatus": "added"
}
]
}
Update Event
PUT /zoho-calendar/api/v1/calendars/{calendar_uid}/events/{event_uid}?eventdata={json}
Required Fields:
- dateandtime - Start and end times
- etag - Current etag value (from Get Event Details)
Optional Fields: Same as Create Event
Example:
python <<'EOF'
import urllib.request, os, json, urllib.parse
from datetime import datetime, timedelta
start_time = datetime.utcnow() + timedelta(hours=2)
end_time = start_time + timedelta(hours=1)
eventdata = {
"title": "Updated Team Meeting",
"dateandtime": {
"timezone": "America/Los_Angeles",
"start": start_time.strftime("%Y%m%dT%H%M%SZ"),
"end": end_time.strftime("%Y%m%dT%H%M%SZ")
},
"etag": 1770368451507
}
url = f'https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727/events/[email protected]?eventdata={urllib.parse.quote(json.dumps(eventdata))}'
req = urllib.request.Request(url, method='PUT')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Delete Event
DELETE /zoho-calendar/api/v1/calendars/{calendar_uid}/events/{event_uid}
Required Header:
- etag - Current etag value of the event
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.maton.ai/zoho-calendar/api/v1/calendars/fda9b0b4ad834257b622cb3dc3555727/events/[email protected]', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('etag', '1770368451507')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
json
{
"events": [
{
"uid": "[email protected]",
"estatus": "deleted",
"caluid": "fda9b0b4ad834257b622cb3dc3555727"
}
]
}
Attendees
When creating or updating events, include attendees:
{
"attendees": [
{
"email": "[email protected]",
"permission": 1,
"attendance": 1
}
]
}
Permission levels: 0 (Guest), 1 (View), 2 (Invite), 3 (Edit) Attendance: 0 (Non-participant), 1 (Required), 2 (Optional)
Reminders
{
"reminders": [
{
"action": "popup",
"minutes": 30
},
{
"action": "email",
"minutes": 60
}
]
}
Actions: email, popup, notification
Recurring Events
Use the rrule field with iCalendar RRULE format:
{
"rrule": "FREQ=DAILY;COUNT=5;INTERVAL=1"
}
Examples:
- Daily for 5 days: FREQ=DAILY;COUNT=5;INTERVAL=1
- Weekly on Mon/Tue: FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU;UNTIL=20250817T064600Z
- Monthly last Tuesday: FREQ=MONTHLY;INTERVAL=1;BYDAY=TU;BYSETPOS=-1;COUNT=2
Code Examples
JavaScript
const response = await fetch(
'https://gateway.maton.ai/zoho-calendar/api/v1/calendars',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
Python
import os
import requests
response = requests.get(
'https://gateway.maton.ai/zoho-calendar/api/v1/calendars',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
Notes
- Event and calendar data is passed as JSON in the
eventdataorcalendarDataquery parameter - Date/time format for events:
yyyyMMdd'T'HHmmss'Z'(GMT) oryyyyMMddfor all-day events - The
rangeparameter for listing events cannot exceed 31 days - The
etagis required for update and delete operations - always get the latest etag before modifying - For delete operations, the
etagmust be passed as a header, not a query parameter - IMPORTANT: When using curl commands, use
curl -gwhen URLs contain brackets to disable glob parsing - IMPORTANT: When piping curl output to
jqor other commands, environment variables like$MATON_API_KEYmay not expand correctly in some shell environments
Error Handling
| Status | Meaning |
|---|---|
| 400 | Missing Zoho Calendar connection, missing required parameter, or invalid request |
| 401 | Invalid or missing Maton API key, or OAuth scope mismatch |
| 404 | Resource not found |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Zoho Calendar API |
Common Errors
| Error | Description |
|---|---|
ETAG_MISSING |
etag header required for delete operations |
EXTRA_PARAM_FOUND |
Invalid parameter in request |
INVALID_DATA |
Malformed request data |
Troubleshooting: API Key Issues
- Check that the
MATON_API_KEYenvironment variable is set:
echo $MATON_API_KEY
- 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-calendar. For example:
- Correct:
https://gateway.maton.ai/zoho-calendar/api/v1/calendars - Incorrect:
https://gateway.maton.ai/api/v1/calendars