---
name: zoho-crm-oauth-app
description: The Zoho CRM layer on top of the shared `zoho-api-console-oauth` skill — the CRM scope families (modules, settings, users, org, bulk, coql, notifications) and the two-level module scope grammar, standard versus custom modules and why the module list must be discovered at run time, the v2-to-v8 API version ladder and what the version in your URL actually changes, the Production / Sandbox / Developer environment split that makes every token organization-specific, and the credit-and-concurrency limit model that belongs to the customer's org rather than to your client. Use when asked to get Zoho CRM OAuth credentials, choose Zoho CRM scopes, debug a Zoho CRM authorization or INVALID_OAUTHTOKEN error, or explain why a Zoho CRM connection cannot see a module. Read `zoho-api-console-oauth` first for the console and data-centre mechanics; use the Zoho Books or Zoho Recruit skill for those products.
---

# Zoho CRM OAuth2 App Registration

Registering the client is the easy part and the base skill covers it. What is specific to CRM is **the scope
vocabulary, and the fact that the thing you are scoping is not fixed.** CRM's data model is whatever the customer
made it: standard modules they may have renamed, custom modules you have never heard of, and fields that differ per
org. A scope list written against one customer's CRM is a guess about the next one's.

Two CRM-specific traps beyond that. **Every environment is a different organization** — a token for Production is
not a token for Sandbox, and there is no flag to switch. And **the limits are denominated in credits, not calls**,
against the customer's org, where a single unlucky operation can cost 500.

## Built on: `zoho-api-console-oauth`

**Read `zoho-api-console-oauth` first, then this file.** It owns all the console and protocol mechanics, and this
file does not repeat them:

- The Zoho API Console, client types, and why a multi-tenant connector needs **Server-based**, not Self Client.
- **The data-centre model** — `location` / `accounts-server` on the callback, the per-DC accounts and API hosts,
  the Multi DC toggle, and the rule that **one client ID serves every DC while the client secret is per-DC by
  default**.
- Redirect-URI rules and the platform's per-data-center callback list.
- The generic scope grammar `service.scope.OPERATION` and the fact that scopes are **comma-delimited**.
- `access_type=offline` plus `prompt=consent`, and the **20-refresh-tokens-per-user-per-client** cap that silently
  revokes the oldest.
- The `Authorization: Zoho-oauthtoken {token}` header form, and taking the API host from `api_domain`.
- Capturing and rotating the secret, and limits being a per-organization concept.

If you are reading only this file, you are missing all of the above — including the single most common cause of a
Zoho CRM integration that works in testing and fails for a European customer.

## Inputs to collect before you start

The base skill lists the console inputs. These are the CRM-specific ones:

| Input | Notes |
| --- | --- |
| **Which CRM modules must the integration reach?** | Decides module-level versus `modules.ALL` (§1) |
| **Are custom modules in scope?** | They need their own scope and cannot be named in advance (§2) |
| **Read-only, or read-write?** | `READ` versus `ALL` per module (§1) |
| **Does it need field or layout metadata?** | Almost always yes, and that is a separate scope family (§1) |
| **Will it use COQL or the Bulk APIs?** | Each is its own scope, not implied by `modules` (§1) |
| **Production only, or Sandbox / Developer too?** | Separate organizations, separate tokens, separate hosts (§4) |
| **What edition are the target customers on?** | Sets their credit ceiling, which your sync design has to fit (§5) |

## Quick Start

1. Work the base skill: Server-based client, redirect URIs, Multi DC, secrets.
2. Settle the module list with the user, then build the scope set from §1 — narrowest that does the job.
3. Add the metadata scope; a connector that maps fields needs it and will otherwise fail on the second call (§1).
4. If custom modules matter, add the custom-module scope and plan to **discover** modules at run time (§2).
5. Pick and pin an API version explicitly; do not let it float (§3).
6. Decide whether Sandbox or Developer environments are in scope — if so, they are separate connections (§4).
7. Size the sync against the customer's credit ceiling, not against yours (§5).
8. Run the base skill's round trip, plus the CRM checks in §6.

## CRM platform state (verified 2026-09-20 — re-verify before trusting)

- **v8 is the current documented API version.** The documentation trees for `v2`, `v2.1`, `v3`, `v4`, `v5`, `v6`,
  `v7` and `v8` all still served pages on 2026-09-20; `v9` did not exist. The base path is
  `{api_domain}/crm/{version}/…`.
- **Zoho published no Zoho CRM API end-of-life date that could be verified on 2026-09-20.** A widely-cited
  "V2 end-of-life extended to 31 December 2026" announcement is **Zoho Projects**, not Zoho CRM — it is signed by the
  Zoho Projects team. Do not repeat it as a CRM fact. See "Unverified" below.
- **Limits are credits, not calls**, over a rolling 24-hour window, plus a concurrency limit and a sub-concurrency
  limit for expensive calls. Free edition is 5,000 credits/day (§5).
- **Three environments** — Production, Sandbox, Developer — each with its own API host prefix and its own tokens (§4).
- `X-API-CREDITS-REMAINING` appears in responses once the org has spent 50% or more of its daily allowance.

If the CRM docs do not look like this, stop and report what you actually see.

## 1. The CRM scope families

CRM's scope vocabulary has seven families. The service name is `ZohoCRM`, and the operation types are `ALL`, `READ`,
`CREATE`, `UPDATE`, `DELETE`.

| Family | What it reaches | Shape |
| --- | --- | --- |
| **`modules`** | Record data — Leads, Contacts, Accounts, Deals, and everything else | `ZohoCRM.modules.ALL`, or `ZohoCRM.modules.{module}.{op}` |
| **`settings`** | Configuration and metadata: fields, layouts, modules, custom views, related lists, roles, profiles, tags, territories, currencies, macros, variables, custom links and buttons, tab groups | `ZohoCRM.settings.ALL`, or `ZohoCRM.settings.{area}.{op}` |
| **`users`** | The org's users | `ZohoCRM.users.ALL` |
| **`org`** | Organization-level details | `ZohoCRM.org.ALL` |
| **`bulk`** | The asynchronous Bulk Read / Bulk Write APIs | `ZohoCRM.bulk.ALL`, `.READ`, `.CREATE` |
| **`coql`** | The SQL-like query API | `ZohoCRM.coql.READ` |
| **`notifications`** | Change notifications (webhooks) | `ZohoCRM.notifications.{op}` |

**The module family nests one level; the others mostly do too.** `ZohoCRM.modules.leads.READ` scopes one module;
`ZohoCRM.settings.fields.READ` scopes one settings area. Zoho publishes the documented module scope names —
`approvals`, `leads`, `accounts`, `contacts`, `deals`, `campaigns`, `tasks`, `cases`, `events`, `calls`, `solutions`,
`products`, `vendors`, `pricebooks`, `quotes`, `salesorders`, `purchaseorders`, `invoices`, `custom`, `dashboards`,
`notes`, `activities`, `search`, `services`, `appointments`, `appointments_rescheduled_history` — and the documented
settings areas: `ALL`, `territories`, `custom_views`, `related_lists`, `modules`, `variables`, `tags`, `tab_groups`,
`fields`, `layouts`, `macros`, `custom_links`, `custom_buttons`, `roles`, `profiles`, `currencies`.

Four things to get right:

- **Metadata is a separate scope, and you almost certainly need it.** Anything that maps CRM fields to a unified
  model reads field metadata, which is `ZohoCRM.settings.fields.READ` — *not* covered by any `modules` scope. A
  connector that requests only module scopes authorizes cleanly and then fails on its first metadata call. Layouts
  (`settings.layouts`) are the same story for anything reading pipelines or stages.
- **COQL and Bulk are not implied by `modules`.** If the connector queries with COQL, `ZohoCRM.coql.READ` must be in
  the list even though every record it returns is a module record. Same for the Bulk APIs.
- **`modules.ALL` is a big ask and an easy sell.** It covers every module including custom ones, and it is what a
  general-purpose connector ends up wanting. It also means a customer's security review sees "full access to all CRM
  data". Where the product genuinely only touches four modules, name those four — it is a materially easier
  conversation and it is free to do.
- **Write access has no middle gear in practice.** `ZohoCRM.modules.leads.CREATE` and `.UPDATE` exist, but a
  connector that creates, updates *and* reads a module usually ends up requesting `.ALL` rather than three strings.
  That is a legitimate simplification; just be aware `.ALL` includes `DELETE`.

**Zoho's published scope list is not exhaustive.** Scope names appear in the wild — and in working connectors — that
the scopes page does not list. If a scope you believe in is rejected with `Invalid OAuth scope`, the published list
is the wrong place to conclude it does not exist; check the specific API's own documentation page, which states its
required scope at the top of each endpoint. If a scope you did *not* find documented is in use somewhere, treat it
as unverified rather than as evidence the page is wrong.

## 2. Modules, custom modules, and API names

**CRM's module list is per-customer.** Standard modules can be renamed, disabled, or hidden by profile; custom
modules are created by the customer and named by them. Nothing about a specific customer's module set is knowable at
registration time.

- **Address everything by `api_name`, never by label.** Every module, field and related list has an `api_name` that
  Zoho generates internally and that survives the customer renaming the label. Labels are display text and will
  change under you. Two documented quirks: the Events module's singular and plural labels are **Meeting** and
  **Meetings**, and standard modules' API names cannot be altered — only custom ones can.
- **Discover the modules at run time.** `GET /settings/modules` returns the list for that org, and it is the only
  correct source. It needs `ZohoCRM.settings.ALL` or `ZohoCRM.settings.modules.{op}`. Building a connector against a
  hardcoded module list is building against your own test org.
- **Custom modules need `ZohoCRM.modules.custom`** — or `modules.ALL`. There is no way to scope one specific custom
  module at registration time, because you do not know its API name until the customer connects. This is the
  practical reason most CRM connectors end up at `modules.ALL`: per-module scoping and custom-module support are in
  tension, and only one of them can be decided in advance.
- **The module list also tells you what is usable.** The metadata response flags each module — whether records can be
  created, whether it can be converted, its generated type (default, web, custom, linking), and whether it is
  reachable by API at all. Read those flags rather than assuming.
- **Field-level access is not in OAuth.** A user's CRM profile and role decide which fields and records they can see.
  The token is bounded by the *scope* **and** by the authorizing user's permissions, and the narrower of the two
  wins. "The scope is right but the data is missing" is usually a profile problem in the customer's CRM, fixable only
  by them.

## 3. API versions

The version sits in the path — `{api_domain}/crm/{version}/…` — and on 2026-09-20 every tree from `v2` to `v8`
still served documentation. **v8 is the current one.**

What the version actually changes, and what it does not:

- **It does not change OAuth.** The scope strings, the console, the DC model, the header form and the refresh regime
  are the same across versions. A v2 integration and a v8 integration use identical credentials.
- **It changes endpoints, payload shapes and available features.** v8 added, among others, related-records counts,
  rich-text field reads, data-sharing-rule management, workflow and action management, recycle-bin operations and
  record-level email sharing. Older versions simply do not have those endpoints.
- **Pin it.** Put a specific version in the base path and change it deliberately. A connector that builds its path
  from a variable someone can edit will one day be running two versions at once against different customers.
- **Match the docs to the version you run.** Zoho keeps the old documentation trees live, which is helpful and also
  means it is easy to read a v2 page while running v8. Check the version in the URL of the page you are quoting.

## 4. Production, Sandbox and Developer are different organizations

CRM has three environments, and they are isolated at the token level:

| Environment | API host pattern |
| --- | --- |
| Production | `https://www.zohoapis.{domain}` |
| Sandbox | `https://sandbox.zohoapis.{domain}` |
| Developer | `https://developer.zohoapis.{domain}` |

Zoho is explicit: *"the access and refresh token generated for a user becomes organization-specific in an
environment. Thus, you cannot use tokens generated for an organization in one environment to make API calls to the
organization in another environment."*

Consequences that shape the integration, not just the test plan:

- **A Sandbox connection is a separate connection.** Separate authorization, separate refresh token, separate stored
  API host. There is no environment parameter and no way to point a Production token at Sandbox.
- **The user picks the org during consent.** If the authorizing user has more than one organization across
  environments, Zoho shows a picker before the consent screen and the grant token is bound to their choice. If they
  have exactly one, it is chosen silently. **So a customer can connect the wrong org without noticing**, and the
  symptom is an authorization that works perfectly against an empty or unfamiliar dataset. Confirm the org in your
  post-connect check rather than trusting the flow.
- **Every consent consumes a refresh-token slot** (base skill §7). Testing across environments with the same Zoho
  user burns through the 20 faster than anyone expects.
- Sandbox is an admin-managed copy of configuration and optionally data, rebuilt from Production on demand — so its
  module set and field set drift from Production between rebuilds. A mapping verified in Sandbox is evidence, not
  proof.

## 5. Credits, not calls

CRM meters **credits** in a rolling 24-hour window, against the customer's organization. Most calls cost 1 credit;
expensive ones cost far more.

| Edition | Allowed credits per 24h | Ceiling |
| --- | --- | --- |
| Free | 5,000 | 5,000 |
| Standard / Starter | 50,000 + (users × 250) + add-ons | 100,000 |
| Professional | 50,000 + (users × 500) + add-ons | 3,000,000 |
| Enterprise / Zoho One | 50,000 + (users × 1,000) + add-ons | 5,000,000 |
| Ultimate / CRM Plus | 50,000 + (users × 2,000) + add-ons | Unlimited |

Trial editions get the same limits as the paid edition they are trialling.

**The costs that surprise people** (1 credit unless listed):

| Operation | Credits |
| --- | --- |
| Insert / Update / Upsert | 1 per 10 records (max 100 records per call, so max 10) |
| Add / Remove tags | 1 per 50 records |
| Get IDs of deleted records; related-records count | 2 |
| COQL query | 1 (LIMIT ≤ 200), 2 (≤ 1,000), 3 (≤ 2,000) |
| Records fetched with a custom-view id | 3 |
| Convert Lead | 5 |
| Send Mail | 20 |
| Create / Update / Delete a custom field | 10 **per field** |
| **Record count in a module** | **50** |
| **Bulk Read Initialize** | **50** |
| Merge records; mass change owner; mass delete by view | 50 |
| Mass Convert Leads | 200 |
| **Bulk Write Initialize** | **500** |
| Create custom module; transfer records and delete user | 500 |

Read that table as a design brief. **"How many records are in this module?" costs 50 credits** — a naive sync that
asks before each page can spend a Free-edition customer's entire day on counting. **Bulk Write Initialize costs
500**, so bulk is cheap per record and expensive per job: batch hard, never per-record. And writes are metered per
ten records, so 100-record pages are ten times cheaper than 10-record pages for identical data.

**Concurrency is a second, independent limit**: 5 (Free) / 10 (Standard) / 15 (Professional) / 20 (Enterprise) /
25 (Ultimate) simultaneous calls per org per app. A separate **sub-concurrency limit of 10**, for all editions,
applies to the expensive calls — COQL, Composite, Convert Lead, Send Mail, sorted or custom-view reads, and multi-
record writes. Exceeding either returns `TOO_MANY_REQUESTS`. Zoho is explicit that there is no per-minute rate limit:
throughput is bounded by concurrency, so the correct lever is fewer parallel calls, not sleeping between them.

Watch `X-API-CREDITS-REMAINING`, which Zoho starts returning once the org has spent 50% of its daily allowance. And
note that the customer's own Deluge functions and other integrations draw on the same budget — you are a guest.

## Product fact — what the CRM connector asks for

> **As of 2026-09-20, Unified.to's Zoho CRM connector requests**, per unified object rather than as one flat list:
>
> - **A metadata-and-query baseline on nearly every scope set:** `ZohoCRM.settings.fields.READ`,
>   `ZohoCRM.coql.READ` and `ZohoCRM.users.READ`. Field metadata and the COQL query API are treated as foundational
>   — consistent with §1, where neither is implied by a module scope.
> - **One module scope per object**, read sets using `.READ` and write sets using `.ALL` on the same module:
>   `ZohoCRM.modules.contacts`, `.accounts`, `.deals`, `.leads`, `.notes`. Pipelines additionally request
>   `ZohoCRM.settings.layouts.READ` and a pipeline settings scope.
> - **Identity/login only:** `ZohoCRM.users.READ`.
> - **Metadata write** escalates the field scope to `ZohoCRM.settings.fields.ALL`.
>
> Three things to raise with the connector's owner rather than copy:
>
> 1. **It does not request `ZohoCRM.modules.ALL`.** Per-module scoping is deliberate and is the easier customer
>    conversation — but it means **custom modules are out of reach** (§2), and adding them later is a re-consent for
>    every customer.
> 2. **At least two of the scope strings in use do not appear on Zoho's published scopes page** — an activity-style
>    module scope and a pipeline settings scope. Either the published list is incomplete (likely; see §1) or those
>    strings are historical. Confirm before reusing them in a new registration.
> 3. **The connector pins the CRM path to a specific API version** (v8 at the time of writing) on top of the
>    `api_domain` returned at token exchange, and **derives the accounts host from the callback's DC parameters** —
>    so it is DC-correct by construction. Its in-console registration help link, however, still points at an older
>    API version's page.
>
> Scope sets change. This note is dated, not live; confirm the current set with the connector's owner before
> submitting anything.

## 6. CRM-specific end-to-end checks

On top of the base skill's authorize → refresh round trip:

1. Call `GET /settings/modules` and confirm the org's actual module list comes back — this proves the settings scope
   and tells you what you are really integrating with.
2. Read one record from each module the integration claims, using its `api_name`.
3. Read **field metadata** for one module. This is the call that fails when only module scopes were requested.
4. If custom modules are in scope, test against a real custom module in a real customer-shaped org — not a standard
   module renamed.
5. Confirm the authorizing user landed in the **organization you expect** (§4), especially if they have more than one.
6. If COQL or Bulk are used, exercise one of each; their scopes are separate and fail separately.
7. Check a response for `X-API-CREDITS-REMAINING` and sanity-check your sync's credit cost against §5.

| Symptom | Cause |
| --- | --- |
| Auth succeeds, field-metadata call fails | `ZohoCRM.settings.fields.READ` not requested (§1) |
| Auth succeeds, COQL or Bulk calls fail | Those are separate scope families, not implied by `modules` (§1) |
| A custom module is invisible | `modules.custom` or `modules.ALL` not requested (§2) |
| A standard module is invisible | Disabled in that org, or hidden by the authorizing user's profile (§2) |
| Scope is right but records or fields are missing | The authorizing user's CRM profile/role, not OAuth (§2) |
| `INVALID_OAUTHTOKEN` | Access token expired, or pushed out by the per-refresh-token access-token cap — reuse the token for its full hour (base skill §7) |
| Everything authorizes but the data is unfamiliar or empty | Connected to the wrong organization or the wrong environment (§4) |
| `TOO_MANY_REQUESTS` | Concurrency or sub-concurrency, not the daily budget — reduce parallelism (§5) |
| Credits exhausted early in the day | A per-page count or per-record write pattern; or the customer's other integrations (§5) |

## Stop and ask

Beyond the base skill's list, hand back to a human when:

- The choice between per-module scopes and `modules.ALL` is open — it is a customer-trust decision and a
  custom-module capability decision at once, and changing it later re-consents every customer (§1, §2).
- Anyone wants a commitment about **custom module support** without a plan for run-time module discovery (§2).
- Sandbox or Developer environments are in scope and nobody has decided whether they are separate connections (§4).
- A customer is hitting credit limits and the answer would be a volume or cost commitment (§5).
- Someone cites a **Zoho CRM API end-of-life date** — verify it against a Zoho CRM source before acting; the widely
  circulated one is Zoho Projects' (see Platform state).

## Unverified as of 2026-09-20

- **No Zoho CRM API version deprecation or end-of-life date could be confirmed from a Zoho CRM source.** The v2–v8
  documentation trees all serve. Treat any specific CRM sunset date as unverified until it is found on a Zoho CRM
  page.
- **The Zoho CRM V8 announcement carries no visible release date**, so "since when" questions about v8 features are
  unanswered here.
- **Zoho's published CRM scope list appears incomplete** (§1). Scope strings in production use are absent from it.

## References

CRM-specific only; the base skill carries the generic Zoho OAuth references. Verified to resolve on 2026-09-20.

- Zoho CRM V8 API home — https://www.zoho.com/crm/developer/docs/api/v8/
- OAuth 2.0 overview — https://www.zoho.com/crm/developer/docs/api/v8/oauth-overview.html
- Register client (client types, mandatory fields, errors) — https://www.zoho.com/crm/developer/docs/api/v8/register-client.html
- Authorization request (org picker, `location`, `accounts-server`) — https://www.zoho.com/crm/developer/docs/api/v8/auth-request.html
- Access & refresh tokens (per-DC accounts URLs, sandbox/developer hosts) — https://www.zoho.com/crm/developer/docs/api/v8/access-refresh.html
- Token validity — https://www.zoho.com/crm/developer/docs/api/v8/token-validity.html
- **Scopes** — https://www.zoho.com/crm/developer/docs/api/v8/scopes.html
- **API limits (credits, concurrency, sub-concurrency)** — https://www.zoho.com/crm/developer/docs/api/v8/api-limits.html
- Multi DC support — https://www.zoho.com/crm/developer/docs/api/v8/multi-dc.html
- Modules API (`api_name`, module discovery) — https://www.zoho.com/crm/developer/docs/api/v8/modules-api.html
- Field metadata API — https://www.zoho.com/crm/developer/docs/api/v8/field-meta.html
- COQL overview — https://www.zoho.com/crm/developer/docs/api/v8/COQL-Overview.html
- Sandbox — https://www.zoho.com/crm/developer/sandbox.html
