Zoho Connection
Build workflows that read, query, and (with confirmation) write Zoho CRM, People, and Recruit data — with regional OAuth, least-privilege scopes, and metadata-validated queries handled for you.
What you can build
Each row is a working pattern: the questions it answers, the tool your workflow calls, and a ready-made template. Every operation is bounded to the fields and forms your connection's refreshed metadata actually exposes — you never write raw Zoho queries or paths.
docs/dsl/06-examples/ for CLI-driven work (chatterfly validate / chatterfly deploy).| I want to… | Use |
|---|---|
| Capture a CRM lead conversationally | “A prospect from Acme wants a demo — log them.” An Agent collects details, crm_search_leads checks duplicates, and crm_create_lead writes after a Boolean confirmation. Template: Zoho CRM Lead Capture |
| Answer pipeline questions with numbers | “What’s our open pipeline by stage?” “How much is Priya carrying this quarter?” Typed crm_query aggregations (sum, group by stage/owner) over Deals, Accounts, Contacts, and activities. Template: Zoho CRM Sales Review |
| Turn a fuzzy name into a stable record ID | “Pull up Michael Johnson’s record” — even when three Johnsons exist. crm_resolve_entity, hr_resolve_employee, hr_resolve_candidate, hr_resolve_job_opening — exact matches resolve, close matches ask. Template: Zoho People Employee Resolution |
| Query HR records by field or date window | “Who joined before June?” “Which employees are in the Support department?” hr_query_people with one equality filter or a between/lte date window on visible fields. Template: Zoho People Joining Date Review |
| Watch deadlines on a schedule | “Every weekday, tell me whose work permit expires in the next 30 days.” A scheduled trigger plus an hr_query_people date window; the run reports who crosses the threshold. Template: Zoho People Work Permit Sweep |
| List and inspect recruiting pipeline | “Show me our current candidates.” “What openings are still unfilled?” hr_query_recruit over Candidates and Job Openings, plus per-record reads. Template: Zoho Recruit Candidate Review |
| Preview a hire-to-onboarding handoff | “Shanthini accepted — prep her onboarding.” Resolve a Recruit candidate, read their record, and show HR a review — reads only today. Template: Zoho Recruit → People Preview |
| Create/update employees with review | “Add our new hire to Zoho People.” people_create_employee / people_update_employee; the validator requires a Boolean confirmation gate before either write. Template: Zoho People Onboarding |
Quick start
Connect Zoho
Create a connection in the dashboard (Connections → New → Zoho), pick only the capabilities your workflow needs, and complete OAuth in the browser. Then confirm it works:
chatterfly connections list
chatterfly connections test <connection-id>Discover what you can call
Operations and their exact JSON-schema parameters come from your connection — browse them instead of guessing names. Refresh metadata so queries validate against your org's real forms and fields:
chatterfly connections operations <connection-id> --product people
chatterfly connections refresh-metadata <connection-id>
chatterfly connections metadata <connection-id>Deploy a working example
Easiest path: open a template from Workflows → New → Templates → Zoho CRM & HR and deploy it from the dashboard. From the CLI, the same definitions live in the repository's docs/dsl/06-examples/ folder. Workflow JSON references a logical connection slug (for example zoho-people); you map it to your real connection at deploy time with --bind:
chatterfly validate docs/dsl/06-examples/zoho-people-employee-resolution.json
chatterfly deploy docs/dsl/06-examples/zoho-people-employee-resolution.json \
--bind zoho-people=<connection-id>Run it
Trigger a test run from the CLI and answer its prompts in the dashboard, or open the workflow's page and start a run there:
chatterfly trigger --deployment-id <deployment-id>
chatterfly runs get <run-id>Setup
- Create a Zoho server-based OAuth client and register
$APP_URL/api/auth/connect/zoho/callbackplus$APP_URL/api/participant/auth/connect/zoho/callback. - Configure a Zoho connection OAuth app in Workspace settings. Each workspace owns and rotates its resource client independently.
- Create a Zoho connection, select CRM, People, or Recruit capabilities, and choose static, caller, or participant fulfillment.
- Authorize through the generated OAuth action. Do not paste access or refresh tokens into workflow JSON.
Capabilities
Capabilities are the unit of consent: each one maps to exact OAuth scopes and materializes a fixed set of operations on the connection. Pick the smallest set that covers your workflow — you can add more later (Zoho will ask you to reauthorize for the added scopes). Use the operation browser, chatterfly connections operations <id>, or MCP list_connection_operations instead of guessing names.
CRM: identity, lead read/write, field metadata
People: employee/form read/write, HR form metadata, leave read, typed HR query
Recruit: job-opening and candidate read, typed HR query
Files: replay-safe protected employee-document uploadIdentity and regions
Every call runs as a verified Zoho identity — you choose which one when you create the connection:
| Fulfillment | Who the provider sees |
|---|---|
| static | A shared workspace account (e.g. an HR service account). Simplest; right for scheduled sweeps and team-level reads. |
| caller | The authenticated user who started the run from your backend. Right for user-attributed writes like lead capture. |
| participant | A workflow participant who authorizes their own grant during the run, pinned to one role. |
At OAuth time the callback probes CRM CurrentUser and pins the Zoho user, organization, granted scopes, region, accounts server, and API domain. At runtime the same authority is re-verified; missing scopes or untrusted routing fail the call instead of silently switching accounts.
Sales intelligence
Ask questions like "open pipeline by stage for this owner" with a typed, validated query instead of raw COQL. Select the CRM read capabilities needed for Deals and activities, plus crm_metadata. Add crm_record_history only to a static workspace connection that needs field-transition evidence.
{
"version": 1,
"module": "Deals",
"metrics": [{ "function": "sum", "field": "Amount", "as": "pipeline_value" }],
"group_by": ["Stage"],
"filter": {
"and": [
{ "field": "Owner", "op": "eq", "value": { "entity_id": "123456789" } },
{ "field": "Stage", "op": "not_in", "value": ["Closed Won", "Closed Lost"] }
]
},
"timezone": "America/New_York"
}Pass this typed IR to crm_query; use crm_customer_query for Accounts or Contacts and crm_activity_query for Events, Tasks, and Calls. Chatterfly validates module fields, operators, limits, and timezone against connection metadata before privately compiling the provider request. Raw COQL, raw search criteria, and arbitrary Zoho paths are not accepted.
- Resolve approximate user, contact, or account names with
crm_resolve_entity. When candidates are ambiguous, show the bounded candidates and require a selection from their returned stable IDs. - Use the confirmed
entity_idin the typed query. This filters records visible to the same connected authority; it never changes the OAuth actor or widens provider permissions. - Configure allowlisted history modules and fields on the connection. Query transitions with
crm_query_transitionsusing bounded RFC3339 dates, an IANA timezone, and at most 200 evidence rows. - Report
indexed_through,lag_seconds, warnings, and truncation with every history-derived claim. Timeline evidence describes past field changes, not current ownership or current record state. - Group pipeline totals by currency and exclude null amounts. Calculate weighted pipeline only when trusted metadata exposes a numeric
Probabilityfield:sum(Amount * Probability / 100). Report included/excluded rows and truncation; never present it as calibrated prediction.
chatterfly connections operations <connection-id> --product crm --query query
chatterfly connections metadata <connection-id>
chatterfly connections history-status <connection-id>
chatterfly connections history-rebuild <connection-id> --module Deals --days 30 --confirmIn a non-production Zoho organization, seed similar owner names, mixed stages, activities, and known stage changes. Validate docs/dsl/06-examples/zoho-crm-sales-review.json, bind sales-crm to the sandbox connection, confirm ambiguous owner selection, compare returned record IDs and transition timestamps with Zoho, and verify stale or partial indexing is disclosed rather than presented as complete.
CRM lead workflow
The canonical write pattern: collect conversationally, check duplicates, show a review, and only write after an explicit yes. The validator enforces this shape for protected operations, so you cannot accidentally ship an unconfirmed write.
Agent(mode=converse) -> deterministic validation
-> crm_search_leads -> participant-visible review
-> Boolean confirmation -> Conditional
-> crm_create_lead- Use a Zoho CRM sandbox or Developer Edition organization. Create a connection with
crm_identity,crm_leads_read,crm_leads_write, andcrm_metadata. These request user read, lead read/create/update, and field-metadata scopes only. - Authorize the salesperson for caller fulfillment, then verify the connection reports the expected actor, organization, region, granted scopes, and no scope gaps.
- Refresh metadata and inspect
crm_create_lead. Use the returned field API names, required fields, picklists, and layout contract; never substitute display labels for API names. - Deploy the validated
docs/dsl/06-examples/zoho-crm-lead-capture.jsonworkflow and bind its logicalsales-crmrequirement to the sandbox connection. - Run duplicate search, participant-visible review, and Boolean confirmation before
crm_create_lead. CRM multi-status failures return typed, redacted provider errors.
chatterfly connections catalog zoho
chatterfly connections operations <connection-id> --product crm --query lead
chatterfly connections metadata <connection-id>
chatterfly connections refresh-metadata <connection-id>
chatterfly deploy docs/dsl/06-examples/zoho-crm-lead-capture.json \
--bind sales-crm=<sandbox-connection-id>Created By or the corresponding provider actor must be the salesperson who authorized the private run, not a browser-selected user or another concurrent account.People onboarding
Create employee records from a reviewed dashboard form, optionally attaching documents — using a shared HR service account so every write is attributed consistently.
- Use a Zoho People sandbox or non-production organization. Create a static workspace connection with
people_employee_readandpeople_employee_write; addpeople_filesonly when the workflow uploads employee documents. - Authorize the shared HR service account. Refresh metadata to discover form links, field API names, required fields, types, and option values before mapping employee data.
- Deploy the validated
docs/dsl/06-examples/zoho-people-onboarding.jsonworkflow and bindhr-peopleto the sandbox connection. - The dashboard form validates employee ID, names, and email, searches for existing employees, displays a review, and writes only after HR confirms.
- For documents, pass a protected run file reference to
people_upload_employee_document. The runtime streams from object storage and reopens the source for a replay-safe retry instead of buffering bytes.
chatterfly connections operations <connection-id> --product people --query employee
chatterfly connections metadata <connection-id>
chatterfly deploy docs/dsl/06-examples/zoho-people-onboarding.json \
--bind hr-people=<sandbox-connection-id>HR reads and resolution
The HR read surface follows one rhythm: refresh metadata, then query or resolve against what it exposes. Metadata is your org's contract — form link names, visible field API names, and cached entity directories — so queries validate before they ever reach Zoho.
chatterfly connections refresh-metadata <connection-id>
chatterfly connections operations <connection-id> --product people --query hr
chatterfly connections operations <connection-id> --product recruit| Tool | What it does |
|---|---|
| hr_query_people | Metadata-validated People form query: selected visible fields, one equality filter or a between/lte ISO date window on a visible date field, bounded pagination, required IANA timezone. |
| hr_query_recruit | Bounded Candidates or JobOpenings list query over visible fields. No filter dialect yet — page through and post-process in the workflow. |
| hr_resolve_employee | Resolve an approximate employee name to a stable People record ID from the cached, complete employee directory. Exact matches resolve; close matches return candidates to confirm. |
| hr_resolve_candidate / hr_resolve_job_opening | The same resolution flow over fresh Recruit metadata. Always confirm a returned stable ID for any non-exact match. |
A typical resolution flow — used by zoho-people-employee-resolution.json — collects a name with an Input, calls the resolver, and branches on resolution: resolved uses the stable ID directly, confirm shows the named candidate for a yes/no, and no_match reports honestly.
Keep page sizes at or below 200. Do not author raw People search criteria, arbitrary provider paths, or a form link based on its display label — Chatterfly maps trusted field API names to provider labels for you, and unsupported query shapes fail closed with a reason instead of guessing.
Private embeds
User-attributed writes require a private run. Your backend authenticates the salesperson, starts the run with an opaque broker grant reference or an explicit transferred authority, and sends only the participant token to the browser. Participant OAuth can also be initiated by the widget for the connection's pinned role. The browser never selects a salesperson or Zoho grant.
- Keep the deployment private and mint a stable
cfw_workflow key for the host backend. - Resolve the authenticated salesperson to a canonical grant, exclusive transfer, or opaque broker reference. Pin the verified provider account, organization, actor, region, and scopes.
- Start
POST /api/v1/runswith anIdempotency-Key, then mint the participant token for the workflow's fixed role. - Give the browser only
runId,participantToken, andapiBase. Test that changing actor/grant fields in browser input has no effect and cross-user authority is rejected.
See the widget guide for private token minting and persistence.
Troubleshooting
invalid_region: the callback returned a domain outside the supported exact regional pairs.insufficient_scope: reconnect with the capability required by the operation.identity_verification_failed: CRM CurrentUser could not verify the connected actor.awaiting_authorization: reconnect the canonical authority, then retry the suspended run.history_lag: transition evidence may omit recent changes; inspect history status and retry after indexing catches up.- Missing transition results: verify the static connection selected
crm_record_history, the module and field are enabled, and the requested period is within retention.
