Zoho CRM API Integration: A Practical Start
From registering a client and getting tokens to core endpoints and safe writes: the first steps of a reliable Zoho integration.

Start a Zoho CRM API integration the right way: client registration, tokens, data centre domains, core endpoints, safe writes and a worked client.
Connecting another system to Zoho CRM usually starts with a simple goal: send leads from a website, push orders from an ERP, or pull deals into a reporting database. The Zoho CRM API makes all of that possible, but the first steps, from registering a client to making a reliable first call, trip up many teams. This guide is a practical starting point for a Zoho CRM API integration: how authentication works, which endpoints you will use most, how to read and write records correctly, the limits to plan for, and a worked example of a small client that reads and upserts records.
Before you start a Zoho CRM API integration
Answer a few design questions first:
- What data flows, in which direction, and how quickly must it arrive?
- Which system owns each field, so updates do not overwrite each other?
- Which identifier links records across systems, such as an email address, an external ID field or the Zoho record ID?
- What happens when a call fails, and who is told?
These answers shape every later decision, and they are much cheaper to settle on paper than in code.
Step 1: register a client and choose scopes
Integrations authenticate with OAuth 2.0. Register a client in the Zoho API console, choosing a server-based client for web applications or a self client for back-end services you own. Request only the scopes you need, for example access to Leads and Contacts rather than every module. Zoho's OAuth overview explains the flow.
Step 2: get tokens and the right domain
Exchange the grant token for an access token, valid for one hour, and a refresh token, which lasts until revoked. Use the accounts server for your data centre, such as accounts.zoho.in for India, and send API requests to the api_domain returned with the token. Cache access tokens and refresh them shortly before expiry rather than requesting a new one for every call.
Step 3: learn the core endpoints
TaskEndpoint patternNotesGet recordsGET /crm/v8/{module}?fields=...The fields parameter is mandatory when listing records, up to 50 fields; up to 200 records per pageGet one recordGET /crm/v8/{module}/{id}Use when you already know the IDInsert recordsPOST /crm/v8/{module}Up to 100 records per callUpdate recordsPUT /crm/v8/{module}Up to 100 records per call, by IDUpsert recordsPOST /crm/v8/{module}/upsertInsert or update based on duplicate check fieldsQuery recordsPOST /crm/v8/coqlSQL-like queries, up to 2,000 records per callChanges since a timeGET with If-Modified-Since headerEfficient delta syncs
Zoho's Get Records documentation explains pagination: page and per_page reach the first 2,000 records, and a page token is needed beyond that.
Step 4: write records the safe way
Upsert is usually the right write operation for integrations. You choose duplicate check fields, such as an email address or a custom external ID field marked as unique, and Zoho updates the existing record if one matches or creates a new one if not. That makes repeated sends harmless and avoids duplicates when an integration retries after a failure.
Batch writes in groups of up to 100 records, and always read the response record by record. A call can partly succeed: Zoho returns HTTP 207 Multi-Status, with a success or error entry for each record in the same order as your input.
Worked example: a small integration client
The illustrative Python below reads recently modified leads and upserts contacts from an external system, checking each record's result. Token handling is simplified; see the comments.
import requests
class ZohoCRM:
def __init__(self, api_domain, token_provider):
self.base = f"{api_domain}/crm/v8"
self.token = token_provider # returns a cached, valid access token
def _headers(self, extra=None):
h = {"Authorization": f"Zoho-oauthtoken {self.token()}"}
h.update(extra or {})
return h
def modified_since(self, module, fields, since_iso):
params = {"fields": ",".join(fields), "per_page": 200}
r = requests.get(f"{self.base}/{module}", params=params, timeout=60,
headers=self._headers({"If-Modified-Since": since_iso}))
if r.status_code in (204, 304):
return [] # no records changed since then
r.raise_for_status()
return r.json().get("data", [])
def upsert(self, module, records, check_fields):
results = []
for i in range(0, len(records), 100):
body = {"data": records[i:i + 100], "duplicate_check_fields": check_fields}
r = requests.post(f"{self.base}/{module}/upsert", json=body,
headers=self._headers(), timeout=60)
if r.status_code not in (200, 201, 207):
r.raise_for_status()
for rec, res in zip(records[i:i + 100], r.json()["data"]):
results.append((rec.get("Email"), res["status"], res.get("code")))
return results
crm = ZohoCRM("https://www.zohoapis.in", token_provider=get_access_token)
for email, status, code in crm.upsert("Contacts",
[{"Last_Name": "Rao", "Email": "[email protected]", "Lead_Source": "Website"}],
["Email"]):
print(email, status, code)
In production, add retries with backoff for HTTP 429 and server errors, log every failed record with its error code, and handle pagination beyond the first page.
Plan for limits from day one
API calls consume credits from a daily allowance that depends on your edition and licences, and there are concurrency limits per edition. Inserting or updating records costs one credit per ten records, so batching matters. Design for limits early: batch writes, use delta syncs instead of full scans, and queue work so bursts are smoothed out.
Common first-integration mistakes
- Generating a new access token for every request
- Hard-coding the US API domain for an organisation hosted elsewhere
- Inserting instead of upserting, creating duplicates on retries
- Ignoring record-level errors inside a successful HTTP response
- Running integrations under a personal user account
- Polling entire modules every few minutes instead of syncing changes
Choosing an identifier strategy
Decide early how records are matched across systems. Email works for contacts but fails for shared inboxes and people who change jobs. A custom external ID field, holding the other system's primary key and marked as unique in Zoho, is the most robust option, and it doubles as the duplicate check field for upserts. Store the Zoho record ID back in the other system too, so updates and lookups never depend on fuzzy matching. For accounts, a normalised website domain is often a better match key than the company name, which varies in spelling and suffixes.
Handling errors by type
Treat errors differently depending on their cause. Authentication errors usually need a token refresh or re-authorisation. HTTP 429 means you have hit a credit or concurrency limit, so slow down and retry later. Record-level errors such as DUPLICATE_DATA or MANDATORY_NOT_FOUND need the data fixed, so retrying will not help; send them to a review list. Server errors can be retried a few times with increasing delays.
Testing your integration
Use a sandbox organisation where possible, with separate credentials. Test with realistic data, including missing fields, long values, special characters and duplicates. Force failures, such as an expired token or an invalid picklist value, and confirm they are logged and reported clearly.
Going live and beyond
Start with a small batch of real data, check the results in the CRM, then increase volume. Keep a simple runbook describing what the integration does, where its credentials live and how to replay failed records. Review error logs regularly during the first weeks, then set alerts so problems surface without someone having to look.
Once the basics work, our guide to Zoho CRM API limits covers scaling within credits and concurrency. If you are moving away from connector platforms, see our comparison of Zapier and custom API integration.
Frequently Asked Questions
How do I start a Zoho CRM API integration?
Register a client in the Zoho API console, request the scopes you need, exchange a grant token for access and refresh tokens, and call the API using the domain returned with the token.
Which Zoho CRM API version should we use?
Use the current version in Zoho's documentation for new integrations. At the time of writing, the documentation covers version 8.
How do we avoid duplicate records?
Use the upsert endpoint with duplicate check fields, such as email or a unique external ID, so repeated sends update existing records.
What does HTTP 207 mean in a Zoho CRM response?
It means the request partly succeeded. Check each record's status and code in the response to see which records failed and why.
Build your Zoho integration with us
We design and build Zoho CRM API integrations with proper authentication, error handling and limit planning. See our CRM and API integration services, or talk to us about your integration.
About the Author
Manav Sharma is the founder of Insyrge and a CRM solutions architect. He works across Zoho, Salesforce, HubSpot, Odoo and more, designing the CRM setups, integrations and automations that keep sales and operations teams working from the same data.
Need Help Implementing This in Your Business?
Our certified Zoho consultants and automation experts can help you design and deploy custom workflows tailored to your operations.
Book Free Consultation