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:
{
"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:
{
"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": "billing@acme.com",
"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:
{
"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:
{
"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: 0
and a message
field
- Dates should be in
yyyy-mm-dd
format
- Contact types are
customer
or vendor
- Some modules (items, chart of accounts, bank accounts, projects) may require additional OAuth scopes. If you receive a scope error, contact Maton support at support@maton.ai 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 -g
when URLs contain brackets to disable glob parsing
- IMPORTANT: When piping curl output to
jq
or other commands, environment variables like $MATON_API_KEY
may 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_KEY
environment 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
Resources