Zoho Books
Access 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.
Quick Start
# List contacts
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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
Base URL
https://gateway.maton.ai/zoho-books/books/v3/{endpoint}
The gateway proxies requests to www.zohoapis.com/books/v3 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 Books 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-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
Create Connection
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'zoho-books'}).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-books",
"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 Books 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-books/books/v3/contacts')
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
Available Modules
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 |
Contacts
List Contacts
GET /zoho-books/books/v3/contacts
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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:
json
{
"code": 0,
"message": "success",
"contacts": [...],
"page_context": {
"page": 1,
"per_page": 200,
"has_more_page": false,
"sort_column": "contact_name",
"sort_order": "A"
}
}
Get Contact
GET /zoho-books/books/v3/contacts/{contact_id}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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
Create Contact
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 contact
- contact_type - Either customer or vendor
Optional Fields:
- company_name - Legal entity name
- email - Email address
- phone - Phone number
- billing_address - Address object
- payment_terms - Days for payment
Example:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"contact_name": "Acme Corporation",
"contact_type": "customer",
"company_name": "Acme Corp",
"email": "[email protected]",
"phone": "+1-555-1234"
}).encode()
req = urllib.request.Request('https://gateway.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:
json
{
"code": 0,
"message": "The contact has been added.",
"contact": {
"contact_id": "8527119000000099001",
"contact_name": "Acme Corporation",
"company_name": "Acme Corp",
"contact_type": "customer",
...
}
}
Update Contact
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://gateway.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 Contact
DELETE /zoho-books/books/v3/contacts/{contact_id}
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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:
json
{
"code": 0,
"message": "The customer has been deleted."
}
Invoices
List Invoices
GET /zoho-books/books/v3/invoices
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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 Invoice
GET /zoho-books/books/v3/invoices/{invoice_id}
Create Invoice
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 identifier
- line_items - Array of items with item_id or manual entry
Optional Fields:
- invoice_number - Auto-generated if not specified
- date - Invoice date (yyyy-mm-dd format)
- due_date - Payment due date
- discount - Percentage or fixed amount
- payment_terms - Days until due
Update Invoice
PUT /zoho-books/books/v3/invoices/{invoice_id}
Delete Invoice
DELETE /zoho-books/books/v3/invoices/{invoice_id}
Invoice Actions
# 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
Bills
List Bills
GET /zoho-books/books/v3/bills
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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
Create Bill
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 identifier
- bill_number - Unique bill number
- date - Bill date (yyyy-mm-dd)
Update Bill
PUT /zoho-books/books/v3/bills/{bill_id}
Delete Bill
DELETE /zoho-books/books/v3/bills/{bill_id}
Expenses
List Expenses
GET /zoho-books/books/v3/expenses
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://gateway.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
Create Expense
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 ID
- date - Expense date (yyyy-mm-dd)
- amount - Expense amount
- paid_through_account_id - Payment account ID
Optional Fields:
- description - Expense details
- customer_id - Billable customer ID
- is_billable - Boolean for billable expenses
- project_id - Associated project
Update Expense
PUT /zoho-books/books/v3/expenses/{expense_id}
Delete Expense
DELETE /zoho-books/books/v3/expenses/{expense_id}
Sales Orders
List Sales Orders
GET /zoho-books/books/v3/salesorders
Create Sales Order
POST /zoho-books/books/v3/salesorders
Purchase Orders
List Purchase Orders
GET /zoho-books/books/v3/purchaseorders
Create Purchase Order
POST /zoho-books/books/v3/purchaseorders
Credit Notes
List Credit Notes
GET /zoho-books/books/v3/creditnotes
Recurring Invoices
List Recurring Invoices
GET /zoho-books/books/v3/recurringinvoices
Recurring Bills
List Recurring Bills
GET /zoho-books/books/v3/recurringbills
Pagination
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.
Code Examples
JavaScript
const response = await fetch(
'https://gateway.maton.ai/zoho-books/books/v3/contacts',
{
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-books/books/v3/contacts',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
Notes
- All successful responses have
code: 0and amessagefield - Dates should be in
yyyy-mm-ddformat - Contact types are
customerorvendor - Some modules (items, chart of accounts, bank accounts, projects) may require additional OAuth scopes. If you receive a scope error, contact Maton support at [email protected] with the specific operations/APIs you need and your use-case
- Rate limits: 100 requests/minute per organization
- Daily limits vary by plan: Free (1,000), Standard (2,000), Professional (5,000), Paid (10,000)
- 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 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 |
Common Error Codes
| 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 |
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-books. For example:
- Correct:
https://gateway.maton.ai/zoho-books/books/v3/contacts - Incorrect:
https://gateway.maton.ai/books/v3/contacts