Install
openclaw skills install zoho-booksZoho Books API integration with managed OAuth. Manage invoices, contacts, bills, expenses, and other accounting data. Use this skill when users want to read, create, update, or delete invoices, contacts, bills, expenses, or other financial records in Zoho Books. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key.
openclaw skills install zoho-booksAccess the Zoho Books API with managed OAuth authentication. Manage invoices, contacts, bills, expenses, sales orders, purchase orders, and other accounting data with full CRUD operations.
# List contacts
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts')
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-books/books/v3/{endpoint}
Maton proxies requests to www.zohoapis.com/books/v3 and automatically injects your OAuth token.
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 Books 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-books&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-books'}).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": "2025-12-08T07:20:53.488460Z",
"last_updated_time": "2026-01-31T20:03:32.593153Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "zoho-books",
"metadata": {}
}
}
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 Books 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-books/books/v3/contacts')
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.
Zoho Books organizes data into modules. Key modules include:
| Module | Endpoint | Description |
|---|---|---|
| Contacts | /contacts | Customers and vendors |
| Invoices | /invoices | Sales invoices |
| Bills | /bills | Vendor bills |
| Expenses | /expenses | Business expenses |
| Sales Orders | /salesorders | Sales orders |
| Purchase Orders | /purchaseorders | Purchase orders |
| Credit Notes | /creditnotes | Customer credit notes |
| Recurring Invoices | /recurringinvoices | Automated recurring invoices |
| Recurring Bills | /recurringbills | Automated recurring bills |
GET /zoho-books/books/v3/contacts
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"code": 0,
"message": "success",
"contacts": [...],
"page_context": {
"page": 1,
"per_page": 200,
"has_more_page": false,
"sort_column": "contact_name",
"sort_order": "A"
}
}
GET /zoho-books/books/v3/contacts/{contact_id}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts/8527119000000099001')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
POST /zoho-books/books/v3/contacts
Content-Type: application/json
{
"contact_name": "Customer Name",
"contact_type": "customer"
}
Required Fields:
contact_name - Display name for the contactcontact_type - Either customer or vendorOptional Fields:
company_name - Legal entity nameemail - Email addressphone - Phone numberbilling_address - Address objectpayment_terms - Days for paymentExample:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"contact_name": "Acme Corporation",
"contact_type": "customer",
"company_name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+1-555-1234"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts', 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
Response:
{
"code": 0,
"message": "The contact has been added.",
"contact": {
"contact_id": "8527119000000099001",
"contact_name": "Acme Corporation",
"company_name": "Acme Corp",
"contact_type": "customer",
...
}
}
PUT /zoho-books/books/v3/contacts/{contact_id}
Content-Type: application/json
{
"contact_name": "Updated Name",
"phone": "+1-555-9999"
}
Example:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"contact_name": "Acme Corporation Updated",
"phone": "+1-555-9999"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts/8527119000000099001', data=data, method='PUT')
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
DELETE /zoho-books/books/v3/contacts/{contact_id}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/contacts/8527119000000099001', 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:
{
"code": 0,
"message": "The customer has been deleted."
}
GET /zoho-books/books/v3/invoices
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/invoices')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
GET /zoho-books/books/v3/invoices/{invoice_id}
POST /zoho-books/books/v3/invoices
Content-Type: application/json
{
"customer_id": "8527119000000099001",
"line_items": [
{
"item_id": "8527119000000100001",
"quantity": 1,
"rate": 100.00
}
]
}
Required Fields:
customer_id - Customer identifierline_items - Array of items with item_id or manual entryOptional Fields:
invoice_number - Auto-generated if not specifieddate - Invoice date (yyyy-mm-dd format)due_date - Payment due datediscount - Percentage or fixed amountpayment_terms - Days until duePUT /zoho-books/books/v3/invoices/{invoice_id}
DELETE /zoho-books/books/v3/invoices/{invoice_id}
# Mark as sent
POST /zoho-books/books/v3/invoices/{invoice_id}/status/sent
# Void invoice
POST /zoho-books/books/v3/invoices/{invoice_id}/status/void
# Email invoice
POST /zoho-books/books/v3/invoices/{invoice_id}/email
GET /zoho-books/books/v3/bills
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/bills')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
POST /zoho-books/books/v3/bills
Content-Type: application/json
{
"vendor_id": "8527119000000099002",
"bill_number": "BILL-001",
"date": "2026-02-06",
"line_items": [
{
"account_id": "8527119000000100002",
"description": "Office Supplies",
"amount": 150.00
}
]
}
Required Fields:
vendor_id - Vendor identifierbill_number - Unique bill numberdate - Bill date (yyyy-mm-dd)PUT /zoho-books/books/v3/bills/{bill_id}
DELETE /zoho-books/books/v3/bills/{bill_id}
GET /zoho-books/books/v3/expenses
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-books/books/v3/expenses')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
POST /zoho-books/books/v3/expenses
Content-Type: application/json
{
"account_id": "8527119000000100003",
"date": "2026-02-06",
"amount": 75.50,
"paid_through_account_id": "8527119000000100004",
"description": "Business lunch"
}
Required Fields:
account_id - Expense account IDdate - Expense date (yyyy-mm-dd)amount - Expense amountpaid_through_account_id - Payment account IDOptional Fields:
description - Expense detailscustomer_id - Billable customer IDis_billable - Boolean for billable expensesproject_id - Associated projectPUT /zoho-books/books/v3/expenses/{expense_id}
DELETE /zoho-books/books/v3/expenses/{expense_id}
GET /zoho-books/books/v3/salesorders
POST /zoho-books/books/v3/salesorders
GET /zoho-books/books/v3/purchaseorders
POST /zoho-books/books/v3/purchaseorders
GET /zoho-books/books/v3/creditnotes
GET /zoho-books/books/v3/recurringinvoices
GET /zoho-books/books/v3/recurringbills
Zoho Books uses page-based pagination:
GET /zoho-books/books/v3/contacts?page=1&per_page=50
Response includes pagination info in page_context:
{
"code": 0,
"message": "success",
"contacts": [...],
"page_context": {
"page": 1,
"per_page": 50,
"has_more_page": true,
"sort_column": "contact_name",
"sort_order": "A"
}
}
Continue fetching while has_more_page is true, incrementing page each time.
const response = await fetch(
'https://api.maton.ai/zoho-books/books/v3/contacts',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
import os
import requests
response = requests.get(
'https://api.maton.ai/zoho-books/books/v3/contacts',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
code: 0 and a message fieldyyyy-mm-dd formatcustomer or vendorcurl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning |
|---|---|
| 400 | Missing Zoho Books connection 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 Books API |
| Code | Description |
|---|---|
| 0 | Success |
| 57 | Not authorized (OAuth scope mismatch) |
| 1 | Invalid value |
| 2 | Mandatory field missing |
| 3 | Resource does not exist |
| 5 | Invalid URL |
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
zoho-books. For example:https://api.maton.ai/zoho-books/books/v3/contactshttps://api.maton.ai/books/v3/contacts