region to the plugin factory: us (default), eu, in, au, jp, cn, ca, sa, sg or ae. To tell which one you are on, check the address you use for Zoho Desk (desk.zoho.eu is eu). You can pass the api_domain from a Zoho token response as apiDomain instead; it is only read to pick the data center, and a value that names no Zoho data center throws when the plugin is created.
orgId header. You rarely need to set it. After OAuth the plugin stores the ID of your organization, and if that step did not run it looks the ID up on the first call and keeps it. If your Zoho user belongs to several organizations it cannot tell which one you picked on the consent screen, so it sends no header and Zoho uses the one the token was issued for. To name one yourself, pass orgId to the factory, or call corsair.zohodesk.keys.set_tenant_external_id(orgId). List them with organizations.list. A mismatch fails with OAUTH_ORG_MISMATCH.
OAuth setup. Create a Server-based Application in the Zoho API Console and add your redirect URL. If the app serves users in several data centers, turn on Multi-DC support there. The plugin requests these scopes:
Access tokens last an hour. The plugin refreshes them when they expire, and once more if Zoho rejects one early. A token passed as
key is never refreshed.
Things to know:
- IDs are strings. Zoho IDs are 64-bit and do not fit a JavaScript number, so inputs take them as strings. Response fields keep the type Zoho sent, and Zoho sends many counts and some booleans as strings.
- Empty lists. Zoho answers
204when a list has nothing in it. List operations return an empty array, andtickets.getLatestThreadandtickets.getResolutionreturnnull. - API credits. Zoho meters calls in daily credits: 1 for most single records, 3 or more for lists, 50 for
agents.getCountanddepartments.getCount, and 1 for every 2 tasks intasks.updateMany. When credits run out and the wait is over a minute, the call fails straight away rather than holding on, with the wait inerror.retryAfter(milliseconds) and the credits left inerror.creditsRemaining. - Concurrency. Zoho also limits simultaneous calls per organization (5 on a trial, up to 25). Calls that hit it retry with backoff.
- Partial updates.
tasks.updateManycan succeed for some tasks and fail for others. Checksuccesson each entry inresults. - Logos.
departments.getLogoreturns base64.departments.uploadLogotakes base64 incontentBase64and afileNameending in jpg, jpeg, png or gif, 2MB at most. - Local sync.
tickets.list,tickets.get,contacts.list,contacts.getandcontacts.getByIdssave rows to the local database. Calls usingfieldsdo not, because a partial row would replace the full one.
- 22 typed API operations
- 2 synced entities (
contacts,tickets) for fast.search()/.list()
Setup
1
Install
2
Add the plugin
corsair.ts
corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- OAuth 2.0 (Recommended)
Open hub.corsair.dev, navigate to your project, and enter the client ID and client secret from your Zoho Desk OAuth app.Set More: OAuth 2.0
region to your Zoho data center (see below). It defaults to us.4
Connect a tenant
Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see Connect / OAuth.
Example API calls
List open ticketsQuery synced data
Tickets are saved when you list or fetch them.contacts, tickets. See Database for filters and operators.
What’s next
API reference
Every
zohodesk.api.* operation with input and output types.Database
Synced entities, search filters, and operators.
Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.