Zoho CRM ExecModule
Overview
ZohoCrmModule connects ValkyrAI workflows to the regional Zoho CRM API v8 for leads, contacts, accounts, deals, and CRM module discovery. It exposes 21 bounded operations through the native map I/O ExecModule ABI while keeping the user-scoped OAuth access token in a READY IntegrationAccount.
The connector selects one of Zoho's documented US, EU, India, Australia, Japan, Canada, China, Saudi Arabia, or UAE API hosts; it never accepts an arbitrary production URL. Requests are capped at 256 KiB, responses at 5 MiB, list pages at 200 records, field lists at 50 names, and JSON nesting at 12 levels. Safe GET operations can retry transient failures up to three total attempts. Create, update, and upsert calls are sent once and require provider receipt and record-ID validation.
The official n8n Zoho CRM integration informed the record-operation surface: its high-value workflows cover accounts, contacts, deals, leads, and provider upserts. ValkyrAI adds regional-host allowlisting, the IntegrationAccount boundary, bounded dynamic field names, explicit pagination contracts, credential redaction, and fail-closed identity checks.
Usage
- Register a Zoho OAuth client and authorize the least-privilege scopes needed by the selected modules.
- Store the current OAuth access token only in the encrypted
apiKeyfield of a ValkyrAIIntegrationAccount, and set the account toREADY. - Bind that account through
ExecModuleConfig.authConfig.integrationAccount. - Set
dataCenterto the region returned by Zoho authorization. - Choose one operation and provide its conditional record, ID, field, or pagination inputs.
- For lists beyond the discrete page range, pass
nextPageTokenunchanged intopageTokenin the next execution.
Inputs
| Name | Type | Required | Description | Constraints |
|---|---|---|---|---|
operation | string | Yes | One of the 21 operations below. | Exact allowlist. |
dataCenter | string | No | Regional Zoho API host selector. | us, eu, in, au, jp, ca, cn, sa, or ae; default us. |
recordId | string | Conditional | Provider ID for get_* and update_*. | 6–30 decimal digits. |
record | object | Conditional | Zoho field API names and values. | Non-empty; at most 100 top-level fields, 5,000 JSON nodes, 12 levels, and 256 KiB serialized. |
fields | array or comma-separated string | List only | Fields returned by Zoho. | 1–50 unique safe field API names. |
duplicateCheckFields | array or comma-separated string | Upsert only | Record fields used for duplicate detection. | 1–5 unique names; each must have a value in record. |
page | integer | No | Discrete list page when pageToken is absent. | 1–10; default 1. |
pageToken | string | No | User-scoped continuation token. | Opaque, at most 2,048 characters; mutually exclusive with page. |
perPage | integer | No | Provider page size. | 1–200; default 100. |
Create requirements:
- Leads require a non-empty
Last_Name. - Contacts require a non-empty
Last_Name. - Accounts require a non-empty
Account_Name. - Deals require non-empty
Deal_Name,Stage, andClosing_Date.
Zoho custom fields are supported when their API names match the safe field-name contract. Credential-like names such as accessToken, password, secret, or apiKey are rejected at every JSON depth.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success or error. |
operation | string | Always | Normalized operation. |
resource | string | After validation | Leads, Contacts, Accounts, Deals, or Modules. |
data | object | Single-record success | Verified record or mutation receipt. |
id | string | Record or write success | Verified Zoho record ID. |
action | string | Upsert receipt supplies it | Provider action such as insert or update. |
items | array | List success | One bounded records page or module metadata page. |
count | integer | List success | Items returned in this execution. |
hasMore | boolean | Record list success | Whether Zoho reports another page. |
nextPageToken | string | Token pagination | Opaque provider continuation. |
page | integer | Zoho supplies it | Discrete page number. |
httpStatus | integer | Provider response | HTTP status accepted by the connector. |
attempts | integer | Provider call or failure | HTTP attempts consumed. |
error | object | Failure | Redacted code, message, and retryable fields. |
CRM payloads and outputs are classified restricted. Tokens and provider bodies are not copied into progress logs.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Zoho CRM API v8 |
| Authentication | OAuth 2.0 access token sent as Authorization: Zoho-oauthtoken … |
| Status | Exactly READY |
accountName | Human-readable Zoho tenant/user identity |
apiKey | Encrypted current OAuth access token, 16–4,096 non-whitespace characters |
| Relationship | ExecModuleConfig.authConfig.integrationAccount |
Recommended scopes:
- Use module-specific
ZohoCRM.modules.<module>.<operation>scopes wherever practical. ZohoCRM.modules.ALLis sufficient but broader than most workflows need.list_modulesadditionally needsZohoCRM.settings.modules.READor the corresponding settings scope.
OAuth consent, refresh-token exchange, and token rotation occur outside this module. The workflow runtime receives only the resolved IntegrationAccount secret.
Configuration
Illustrative persisted configuration:
{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:zoho-crm-sales"
},
"payloadConfig": {
"parameters": "{\"dataCenter\":\"eu\",\"operation\":\"list_deals\",\"fields\":[\"Deal_Name\",\"Stage\",\"Amount\"],\"perPage\":100}"
}
}
The relationship is symbolic. The generated IntegrationAccount record owns the encrypted token.
Operations
| Resource | Create | Get | List | Update | Upsert |
|---|---|---|---|---|---|
| Leads | create_lead | get_lead | list_leads | update_lead | upsert_lead |
| Contacts | create_contact | get_contact | list_contacts | update_contact | upsert_contact |
| Accounts | create_account | get_account | list_accounts | update_account | upsert_account |
| Deals | create_deal | get_deal | list_deals | update_deal | upsert_deal |
| Modules | — | — | list_modules | — | — |
Record routing:
- Create:
POST /crm/v8/{Module} - Get:
GET /crm/v8/{Module}/{recordId} - List:
GET /crm/v8/{Module} - Update:
PUT /crm/v8/{Module}/{recordId} - Upsert:
POST /crm/v8/{Module}/upsert - Module discovery:
GET /crm/v8/settings/modules
Delete, search, bulk import/export, COQL, related records, notes, attachments, custom-module writes, workflow notifications, and OAuth refresh are deferred from version 1.0.0.
Errors and Failure Modes
| Failure | Cause | Retry guidance |
|---|---|---|
VALIDATION_ERROR | Missing mandatory field, malformed record ID, unsupported region, unsafe field, invalid JSON, page/token conflict, local size bound, or invalid provider success shape. | Correct the named input. No provider call is sent for local validation failures. |
INTEGRATION_ACCOUNT_REQUIRED | No account is bound. | Bind the intended Zoho account. |
INTEGRATION_ACCOUNT_NOT_READY | Account status is not READY. | Repair or reauthorize it. |
ZOHO_CRM_HTTP_400/404 | Provider validation, field API name, module, page token, or record ID error. | Correct the request; do not blindly replay writes. |
ZOHO_CRM_HTTP_401/403 | Invalid/expired token, OAuth scope mismatch, or user permission denial. | Refresh authorization or narrow/correct scopes. |
ZOHO_CRM_HTTP_429 | Zoho concurrency, credit, or rate limit. | Reads retry with bounded delay. Writes remain single-attempt. |
ZOHO_CRM_HTTP_5xx / NETWORK_ERROR | Provider or transport failure. | Reads retry up to three attempts. Reconcile create/update/upsert state before manual retry. |
RESPONSE_TOO_LARGE | Response exceeded 5 MiB. | Reduce fields or perPage. |
| Identity mismatch | Returned record/receipt ID is invalid or differs from the requested update/get ID. | Treat as a provider-contract failure and investigate. |
Errors are bounded to 500 characters and pass through ValkyrAI's sensitive-data redaction policy.
Example
Upsert a qualified lead by email:
{
"operation": "upsert_lead",
"dataCenter": "us",
"duplicateCheckFields": ["Email"],
"record": {
"Last_Name": "Lovelace",
"Email": "ada@example.com",
"Company": "Analytical Engines",
"Lead_Status": "Qualified"
}
}
Expected normalized result:
{
"status": "success",
"operation": "upsert_lead",
"resource": "Leads",
"id": "5725767000000524157",
"action": "update",
"httpStatus": 200,
"attempts": 1
}
Notes
- Pagination: Zoho returns at most 200 records per request. Pages 1–10 cover the first 2,000 records; later pages use the user-scoped
page_token, which must be passed unchanged and may expire. - Rate limits: Zoho applies concurrency and API-credit controls by edition and API. The connector honors bounded numeric
Retry-Aftervalues and caps safe-read retries at three attempts. - API limits: a list accepts at most 50 fields; the connector adds local 256 KiB request, 5 MiB response, 5,000-node, and 12-level nesting limits.
- Idempotency: reads are retry-safe. Upsert uses explicit duplicate fields but can still complete ambiguously after a timeout, so all writes are single-attempt.
- Destructive behavior: delete is intentionally not exposed. Creates, updates, and upserts can trigger Zoho workflows, assignment rules, blueprints, and downstream automations.
- Dynamic schemas: use
list_modulesand Zoho field metadata outside this version when mapping custom modules and fields; the connector never guesses a display label as an API name. - External verification: deterministic MockWebServer tests cover validation, regional routing, auth headers, request construction, upsert fields, pagination, retries, write non-retry, redaction, identity checks, and annotation discovery. Live Zoho execution is deferred until a separately authorized account is supplied.
See the official Zoho CRM v8 Records API, OAuth scopes, multi-data-center guidance, upsert API, and n8n Zoho CRM integration for provider and functional reference.