Welcome to Old Notebook Digitization! Welcome to the notebook digitization desk. Whether you are testing a page capture rig, puzzling over gutter legibility, or deciding how to name and back up scans, you can browse the threads and ask your own questions here.

Zoho CRM API Integration: OAuth, Limits & Code Examples

  • Thread starter Thread starter Pankaj Sakariya
  • Start date Start date
P

Pankaj Sakariya

Guest

Quick Answer

Zoho CRM API integration lets another app read and write your CRM data over a secure OAuth 2.0 connection. Register a client in the Zoho API Console, get an access token, and call the V8 REST API. Use a connector for simple, standard syncs, and build custom when the logic, volume, or custom modules need more control.


When a connector is enough

  • The sync is simple and standard, like a new form lead becoming a CRM contact
  • You want a faster setup for a supported workflow
  • Someone who isn’t a developer will look after it

When custom API integration wins

  • The logic has branches, conditions, or several steps
  • Volume is high enough that per-task pricing starts to sting
  • You need custom modules or actions your chosen connector does not support

Before you build: check the authorization header, the account’s data center, edition-based concurrency limits, and Notification API channel expiry. The checklist below explains each one.



Connecting Zoho CRM to an online store or accounting tool involves more than moving fields. You need a consistent matching key, rules for conflicting updates, and a way to recover when a request fails.

The API is actually pretty good. The docs are good too, but they’re a reference book. They tell you what exists. They don’t tell you what to pick, and that’s where people get stuck.

So this guide covers both sides. If you’re a developer, you’ll get OAuth and API code examples, plus a gotchas checklist you’ll want to bookmark. If you’re the person deciding whether to build or buy, you’ll get plain answers about what the Zoho CRM REST API can do, how much effort it takes, and when something like Zapier is the smarter call. Quick disclosure: we’re a Zoho Premium Partner. But the build vs buy part is meant to be fair, and there are no affiliate links anywhere in here.


What Zoho CRM API Integration Actually Lets You Do​


Short version: Zoho CRM API integration lets another app read and write your CRM data (leads, contacts, deals, custom modules) over a secure connection. So your CRM can stay in sync with your accounting tool, email platform, online store, or a custom app. This can reduce manual copying between systems.


What does that look like in real life? A few things come up constantly:


A deal closes, and an invoice shows up in your accounting system. Finance stops pinging sales.
Duplicate contacts get cleaned up across tools. One customer, one record. Not three.
A deal moves to a new stage and something happens somewhere else, like an onboarding task or a Slack message.

If your team does any of that by hand right now, this is how you stop.


How Zoho CRM API Authentication Works (OAuth 2.0)​


Ever notice every Zoho tutorial opens with OAuth? There’s a reason. Nothing else works until this does. Here’s the whole setup:

  1. 1​
    Open the Zoho API Console and register a client. Choose “Server based Applications” if people will log in and approve access. Choose “Self Client” for a server-side integration accessing a Zoho account you control. Its initial grant is generated manually; subsequent token refresh can run without a user present.
  2. 2​
    Copy your client ID and client secret.
  3. 3​
    For the examples below, request ZohoCRM.modules.leads.READ and ZohoCRM.modules.contacts.ALL, separated by a comma. For your own integration, limit access to the modules and operations it actually needs.
  4. 4​
    In the Self Client, generate a grant token. It doesn’t last long, so use it right away.
  5. 5​
    Trade that grant token for an access token and a refresh token.

The curl example below uses the Self Client flow. Replace the Accounts URL with the one for your data center. For a server-based app, also supply its registered redirect URI and request access_type=offline during authorization to obtain a refresh token.

bash

Code:
curl -X POST "https://accounts.zoho.com/oauth/v2/token" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=YOUR_GRANT_TOKEN"

You’ll get back an access token, a refresh token, and something called api_domain. Hang on to that last one. It matters in a minute.

Access tokens last an hour. The refresh token doesn’t expire by itself, but someone can revoke it, so treat it like a password. Your code should grab a fresh access token before the old one runs out.

Here’s a small Python helper for token refresh and API calls. Run it on a server with Python 3 and the requests package installed. Set ZOHO_ACCOUNTS_URL, ZOHO_CLIENT_ID, ZOHO_CLIENT_SECRET and ZOHO_REFRESH_TOKEN as environment variables. Use your regional Accounts URL and keep secrets out of browser code and source files.


python

Code:
import os
import time
import requests

ACCOUNTS_URL = os.environ["ZOHO_ACCOUNTS_URL"].rstrip("/")
CLIENT_ID = os.environ["ZOHO_CLIENT_ID"]
CLIENT_SECRET = os.environ["ZOHO_CLIENT_SECRET"]
REFRESH_TOKEN = os.environ["ZOHO_REFRESH_TOKEN"]

_cache = {"token": None, "api_domain": None, "expires_at": 0}

def get_token():
    if _cache["token"] and time.time() < _cache["expires_at"] - 60:
        return _cache["token"], _cache["api_domain"]

    resp = requests.post(
        f"{ACCOUNTS_URL}/oauth/v2/token",
        data={
            "grant_type": "refresh_token",
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "refresh_token": REFRESH_TOKEN,
        },
        timeout=30,
    )
    resp.raise_for_status()
    body = resp.json()
    if "access_token" not in body:  # errors can arrive inside the body
        raise RuntimeError("Token refresh failed. Check OAuth configuration and revocation status.")

    _cache.update(
        token=body["access_token"],
        api_domain=body["api_domain"],
        expires_at=time.time() + body["expires_in"],
    )
    return _cache["token"], _cache["api_domain"]
def crm(method, path, **kwargs):
    token, api_domain = get_token()
    resp = requests.request(
        method,
        f"{api_domain}/crm/v8/{path}",
        headers={"Authorization": f"Zoho-oauthtoken {token}"},
        timeout=30,
        **kwargs,
    )
    resp.raise_for_status()
    if resp.status_code == 204:  # no matching records, no body
        return {}
    return resp.json()

And two quick calls to try it. One reads five leads. The other creates a contact, or updates the existing one if the email already matches.

python

Code:
leads = crm("GET", "Leads", params={"fields": "Last_Name,Email,Company", "per_page": 5})
for lead in leads.get("data", []):
    print(lead["Last_Name"], lead.get("Email"))

result = crm("POST", "Contacts/upsert", json={
    "data": [{"Last_Name": "Rivera", "Email": "dana.rivera@example.com"}],
    "duplicate_check_fields": ["Email"],
})
items = result.get("data", [])
if not items:
    raise RuntimeError("Upsert returned no record results")
item = items[0]
if item.get("status") != "success":
    raise RuntimeError(f"Contact upsert failed: {item.get('code', 'UNKNOWN_ERROR')}")
print(item["code"])

Test with sample records in a suitable test environment. Your layout may require additional fields. Inspect every record result: a batch can return HTTP 207 with both successful and failed operations.

The gotcha almost everyone hits first​


Two things. Both are sneaky.


First, the header. Zoho wants Authorization: Zoho-oauthtoken YOUR_TOKEN. Not Bearer. And here’s the annoying part: the token response literally says token_type: Bearer. So you do what every other API taught you, get a 401, and lose an afternoon.


Second, the data center. Zoho runs separate regions, and every account lives in just one. A .com account, a .eu account, and a .in account all have their own account URL and API domain. Hit the wrong one, and you get an error that looks exactly like a bad token. Your token’s fine. You’re just knocking on the wrong door.


The fix is boring, which is nice. Keep the accounts URL in config. And always read api_domain from the token response instead of hardcoding zohoapis.com.


The Core Zoho CRM APIs You Should Know​


Zoho CRM isn’t one API. It’s a little family of them, and picking the right one saves you credits and a lot of grief. Here’s the cheat sheet.

[th]
API​
[/th]
​
[th]
Use it for​
[/th]
​
[th]
Watch out for​
[/th]
​
[td]REST API[/td] [td]Reading and writing records one at a time or in small batches[/td] [td]Up to 200 records per read, 100 per write[/td] [td]Bulk API (read and write)[/td] [td]Big exports and imports, like migrations and first-time syncs[/td] [td]It’s asynchronous. You wait for a job, then download or check results[/td] [td]Notifications and webhooks[/td] [td]Real-time change events pushed to your URL[/td] [td]Notification channels expire and need renewing[/td] [td]COQL[/td] [td]SQL-style queries with filters and joins across lookups[/td] [td]Use Bulk Read for large asynchronous exports[/td] [td]Functions[/td] [td]Custom logic that runs inside Zoho[/td] [td]Best for small tasks close to the data[/td]

A few notes on those.

The REST API supports everyday create, read, update and delete operations on supported modules. When listing records with GET /{module_api_name}, include the fields parameter with up to 50 field API names. Retrieving a specific record by ID does not have the same fields requirement.

Bulk Read can export up to 200,000 records in one job, delivered as a CSV inside a ZIP. Bulk Write takes up to 25,000 records per file. Both run in the background, so they’re great for migrations and first syncs, and not so great for anything live.

COQL stands for CRM Object Query Language, and it feels a lot like SQL. You POST a query to the /coql endpoint:


json

Code:
{
  "select_query": "select Last_Name, Email from Contacts where Email is not null limit 50"
}

Functions are code that runs inside Zoho, usually written in Deluge. If your logic is small and sits right next to the data, a function can beat hosting your own service. We’ve already covered the basics in our Deluge guide for Zoho customization, so I won’t repeat it.

The examples in this guide use V8 endpoints. Keep the API version consistent across your calls, and check older examples against the version you plan to use. For endpoint details and developer tools, the official Zoho CRM API documentation has you covered.

Real Integration Patterns (and the Thinking Behind Them)​


Endpoint lists are fine, but they don’t tell you how to actually build something. Patterns do. Four of them cover most projects.

CRM to accounting​


Picture a rep moving a deal to Closed Won. Finance needs an invoice. Here’s the flow:

  1. 1​
    A notification (or a workflow webhook) tells your service the deal changed stage.
  2. 2​
    Your service pulls the deal, its account, and its products.
  3. 3​
    It checks whether an invoice already exists, using a custom field on the deal that stores the invoice ID.
  4. 4​
    No invoice yet? It creates one in Zoho Books or QuickBooks.
  5. 5​
    It writes the new invoice ID back onto the deal.

Do not skip the duplicate-invoice check. Use a stable event or external record ID to deduplicate retries, and make sure concurrent events cannot create the same invoice twice. One more thing: the Zoho Books API uses the same OAuth model but has its own scopes, and every call needs your Books organization ID. If you’re dealing with bigger finance and ERP syncs, our Zoho NetSuite integration guide shows how the same ideas scale up.

CRM to marketing​


Syncing contacts with an email platform sounds easy. It’s really a question of who owns what. Decide field by field which system wins. Usually the CRM owns name and company, and the email tool owns subscription status. Use upsert with appropriate duplicate-check fields, and define how changed emails and existing duplicates will be handled.

CRM to ecommerce​


A typical setup goes like this. The store sends a webhook to your service. Your service upserts the customer as a contact, then creates a deal or a custom order record linked to them. Watch out for guest checkouts and missing or changed customer identifiers. Store a stable source-system customer ID and map it to the CRM record. Our Zoho and Shopify integration guide walks through that exact setup.

CRM to a custom app​


Now your app is the other half of the sync. Save the Zoho record ID in your own database from day one. Use notifications to hear about CRM changes, and the REST API to push yours back. Custom modules are where a little planning pays off big. Before you write any code, have a look at our Zoho CRM customization guide to see what’s possible inside the CRM itself.

Rate Limits, Credits, and Performance​


Zoho runs two limit systems at once, and most people only remember one.

Credits first. Every API call costs credits from a daily pool, counted over a rolling 24-hour window. Most calls cost 1. Inserts, updates, and upserts cost 1 credit per 10 records. Converting a lead costs 5. Starting a Bulk Read costs 50, and starting a Bulk Write costs 500.

The Free edition gets 5,000 credits. Paid editions start at 50,000 and add a per-user amount that depends on the edition, so a 100-user Enterprise org ends up with 150,000. Once you’ve used half your daily credits, responses include an X-API-CREDITS-REMAINING header. Handy for monitoring.

Concurrency limits cap the API calls active at the same time for an organization and an app. Configure parallel workers for your edition’s limit, and check the separate sub-concurrency limit for resource-intensive operations.

Concurrent request limits per organization/app:


Free: 5
Standard: 10
Professional: 15
Enterprise or Zoho One: 20
Ultimate or CRM Plus: 25

Specified operations also share a sub-concurrency limit of 10. These include Convert Lead, COQL, Composite requests, record reads using cvid or sort_by, and insert/update/upsert calls containing more than 10 records. Exceeding an applicable concurrency limit can return TOO_MANY_REQUESTS.


Quick math shows why this matters. Updating 10,000 contacts one call at a time burns 10,000 credits. In batches of 100? That’s 100 calls and about 1,000 credits. Big difference.


So how do you stay out of trouble? Batch your writes. Use Bulk APIs for anything large. Cache field metadata instead of fetching it again and again. Retry with exponential backoff and a bit of random jitter. And lean on notifications instead of polling.

Always check the numbers against Zoho’s official API limits page before you size a project. Limits change, and that page changes with them.


The Gotchas That Break Zoho Integrations​


Check these details before taking an integration live.

  1. 1​
    Wrong auth header. Zoho-oauthtoken, not Bearer.
  2. 2​
    Data center mismatch. Match the account’s URL and API domain to the account’s region. Read api_domain from the token response.
  3. 3​
    Concurrency limits. Limit parallel requests for each organization/app combination. Check whether the endpoint also falls under the sub-concurrency limit.
  4. 4​
    Notification expiry. Notification API channels last up to one week. If channel_expiry is omitted or set beyond that maximum, the default is one hour. Renew subscriptions before they expire. This expiry rule applies to Notification API channels, rather than workflow webhooks generally. The Enable Notifications documentation has the details.
  5. 5​
    Token refresh. Cache the access token and refresh shortly before it expires. Monitor refresh failures without logging tokens or client secrets.
  6. 6​
    Missing fields parameter. Include fields when fetching a module’s record list; this is distinct from retrieving a specific record by ID.
  7. 7​
    Pagination limits. Page-number pagination stops at 2,000 records. For more, follow next_page_token using page_token without also sending page. Get Records supports up to 100,000 records; use Bulk Read for larger exports.

Check the data center when authentication fails, and channel expiry when change notifications stop.

Build vs Buy: Custom API Integration or a Connector?​


Not everything needs custom code. Sometimes a connector is faster and cheaper, and anyone telling you otherwise is selling something.


Connectors and iPaaS tools like Zapier, Make, and Zoho Flow shine when the sync is simple and standard. Think “new form lead becomes a CRM contact” or “new Stripe customer becomes a contact.” Supported workflows may be quicker to launch, depending on field mapping and testing. Routine maintenance may not require a developer.


A custom API integration makes more sense when:

  • The logic has branches, conditions, or several steps.
  • Volume is high enough that per-task pricing starts to sting. Check current pricing pages, since plans change.
  • You need custom modules or actions your chosen connector does not support.
  • You want tight control over retries and error handling.

Unified API tools are a different animal. They give you one interface to lots of CRMs, which is great if you’re a SaaS company supporting Zoho, HubSpot, and Salesforce customers. But if Zoho’s all you care about, they put an extra layer between you and things like COQL or custom modules.


My rule of thumb? If the workflow fits on a napkin, start with a connector. When you can already feel the exceptions piling up, build custom.


SDKs vs Raw REST: Which Should You Use?​


You don’t need an SDK. Plain HTTP calls work fine, like the Python example above.

That said, Zoho publishes official SDKs for PHP, Node.js, Java, and Python, plus a few other languages. They’re worth it for a long-running app, since they can handle requests and token refresh once you configure authentication, the correct data center/environment, and secure token persistence.

Raw REST wins for a small serverless function, one or two endpoints, or a language with no official SDK. If you combine SDK and direct REST calls, use a coordinated token-management strategy.


When to Bring in a Zoho Developer​


DIY and connectors have a ceiling. You’ve probably hit it when:

  • Several systems have to stay consistent at once, not just two.
  • Your CRM has custom modules, custom fields, or tangled relationships.
  • Data integrity matters, whether that’s audits, privacy rules, or two-way syncs where conflicts need deliberate handling.
  • A broken sync would cost real money or customer trust.

Nothing scary here. It’s just the point where a good developer saves more time than they cost. If that sounds like your project, our Zoho development services team can help you scope it.


Where to Go From Here​


The Zoho CRM API is capable and well documented. Most integrations win or lose on a short list of boring details: auth, data center, rate limits, and how long tokens and channels live. Get those right, and the rest is regular engineering. Match the approach to the job, start small, and build up.

For the official reference, bookmark the Zoho OAuth documentation and the Zoho API Console. You’ll be back to both.


Frequently Asked Questions​

How do I integrate Zoho CRM with another app using the API?​


Register a client in the Zoho API Console, get an access token through OAuth 2.0, and call the V8 REST API with the Zoho-oauthtoken header. Then map the fields between both systems. Use upsert with suitable matching fields to reduce duplicates, and notifications to react to changes instead of polling.

Does the Zoho CRM API use OAuth 2.0?​


Yes. Zoho CRM uses OAuth 2.0 for all API access. You register a client in the API Console, request scopes, and receive an access token that lasts one hour and a refresh token for renewals. The access token goes in an Authorization header formatted as Zoho-oauthtoken, not Bearer.

What are the Zoho CRM API rate limits?​


Zoho CRM uses credits over a rolling 24-hour window and separate limits on simultaneous requests. Direct API concurrency ranges from 5 for Free to 25 for Ultimate/CRM Plus, with a sub-limit of 10 for specified operations. Check the official limits for your edition before planning.

Should I build a custom Zoho integration or use Zapier?​


Use Zapier, Make, or Zoho Flow for simple, standard syncs you want live quickly. Build custom when you have complex logic, high volume, custom modules, or a need for tight control over errors and retries. If the workflow fits on one page and volume is low, start with the connector.

Do I need a Zoho SDK to use the API?​


No. The REST API works with HTTP calls from any language. Official SDKs can simplify requests and token refresh, but you must configure authentication, the correct data center/environment, and token persistence. Direct REST calls are often sufficient for small scripts.

What is COQL in Zoho CRM?​


COQL stands for CRM Object Query Language. It lets you query Zoho CRM data with SQL style SELECT statements, including filters and lookup field relationships. You send the query to the COQL endpoint of the REST API. It suits targeted searches, while Bulk Read is better for large exports.

Why does my Zoho API call keep failing with an authentication error?​


Common causes include using Bearer instead of the required Zoho-oauthtoken authorization scheme, a mismatch between your account’s data center and API domain, an expired access token, or insufficient scopes. Check these first and use api_domain from the token response.


Ready to Build the Integration?

If your Zoho CRM integration involves custom logic, high volume, or compliance needs, Elsner’s team can build and maintain it for you. Start with a scoping conversation about what you want connected and why. We’ll tell you straight whether a connector will do the job.

Talk to our Zoho development team
​

The post Zoho CRM API Integration: OAuth, Limits & Code Examples first appeared on Elsner Technologies Pvt. Ltd..
 
Top