The Zoho Desk API is a REST interface that returns and accepts JSON, exposing tickets, contacts, accounts, agents, and departments as resources behind OAuth 2.0. Developers use it to sync ticket data with CRMs, build custom escalation rules, mirror ticket events into data warehouses, and power custom support apps for agents and end customers.
The Zoho Desk API exposes tickets, contacts, accounts, agents, and departments as REST resources behind OAuth 2.0. Zoho Desk developers use the API to sync ticket data with CRMs (see the Zoho CRM API developer guide for the matching CRM-side patterns), build custom escalation rules, mirror ticket events into data warehouses, or power custom mobile experiences for support agents and end customers.
This guide walks through every layer a developer needs to ship a production helpdesk integration: registering an OAuth client in the Zoho API console, generating refresh and access tokens, calling the ticket and contact endpoints with the required orgId header, configuring webhooks for ticket events, and handling pagination and refresh-token rotation. Code samples cover curl, Python, and Node.js so the same flow translates to whatever stack you already run.
Before writing a single line of code, confirm two things. First, your Zoho Desk plan tier supports the API endpoints and call volume your integration needs. Second, your data center region matches the URLs in your code. Both items trip up developers more than auth itself, and both are covered in the prerequisites section below.
Overview: What the Zoho Desk API Does
The Zoho Desk API is a REST interface that returns and accepts JSON. Every resource follows a predictable pattern: a base URL specific to your data center region, the path /api/v1/, the resource name, and an optional resource ID. Authentication uses OAuth 2.0 access tokens passed in the Authorization header, and almost every request requires an orgId header that identifies which Zoho Desk organization the call belongs to.
Core resources exposed by the API include tickets, contacts, accounts, departments, agents, ticketComments, threads, articles, and tasks. Tickets are the primary object most integrations focus on - you will read tickets to sync state outward, write tickets to capture support requests from custom forms or third-party channels, and listen to webhooks to react to ticket lifecycle events.
The API supports both the standard three-leg OAuth 2.0 authorization code flow for user-facing apps and a Self-Client flow for server-to-server scripts that do not need a user to grant consent at runtime. Most backend integrations use Self-Client because it produces a long-lived refresh token tied to a service account.
Postman collections are available from Zoho’s developer documentation, which is the fastest way to validate auth and explore endpoints before writing code. The api console and full api documentation live at api-console.zoho.com and desk.zoho.com/DeskAPIDocument respectively. Replace the TLD on those URLs to match your region (.eu, .in, .com.au, etc.) if your organization is hosted outside the US data center.
What Tools Do You Need for the Zoho Desk API?
To follow this guide end to end you need four things in place. A Zoho Desk account on the Standard plan or higher is the recommended starting point because lower tiers cap automation features and constrain the API endpoints available for production use. Free and Express tiers expose the api but with reduced coverage that becomes painful in real integrations.
Postman or any HTTP client of your choice is the second tool - Insomnia and HTTPie are equally capable alternatives. The provided Zoho Postman collection includes pre-built requests for tokens, tickets, contacts, and webhooks, and it sets the Authorization header automatically once you paste a valid access token into the collection variables.
The third item is access to the Zoho api console at api-console.zoho.com. This is where you register the OAuth client your integration will use, generate the initial authorization code, and exchange that code for a refresh token. Treat the api key style client secret produced here as a high-value credential and store it in a secrets manager rather than in source control.
Finally, decide on a runtime stack. The examples in this guide use curl for the raw HTTP shape, Python with the requests library for backend scripts, and Node.js with axios or the native fetch for serverless functions. The same patterns translate cleanly to Go, Ruby, PHP, or any language with a competent HTTP client.
Which Zoho Desk Plan Tier Do You Need for API Access?
Plan tier matters more than the documentation suggests. The Free plan supports up to 3 agents and very basic api access, which is fine for a quick proof of concept but not for production. The Zoho Desk setup guide walks through the UI tier-by-tier. The Express tier at $7 per agent per month annual covers up to 5 agents with social channels and basic automation, but it still constrains the api surface. Standard at $14 per agent per month annual is the minimum tier we recommend for any production helpdesk integration: it unlocks unlimited agents, full automation, and the broadest api endpoint coverage. Professional at $23 per agent per month annual adds multi-department support, and Enterprise at $40 per agent per month annual adds Zia AI and advanced workflows - see the Zoho Desk Zia AI helpdesk guide for what unlocks programmatically. Review the full tier breakdown on the Zoho Desk pricing page before choosing a plan for your integration.
See Zoho Desk pricing tiersRole permissions are the second checkpoint. The OAuth client you create runs under the consenting user’s permissions in user-flow apps, and under the Self-Client owner’s permissions in server-to-server flows. If the consenting user cannot see a department or ticket field in the Zoho Desk UI, the api call will return empty values or 403 errors. Most teams create a dedicated integration user with administrator or department-administrator privileges to avoid this trap.
The orgId is the third prerequisite. Every Zoho Desk organization has a numeric orgId that must be sent in the orgId header on most api requests. Find yours under Setup, then Developer Space, then API. Copy that value once and store it as an environment variable - hardcoding it in multiple places guarantees a refactor later when you spin up a sandbox org.

Last, confirm your data center region. Zoho Desk runs in seven regions, each with its own base URL: desk.zoho.com (US), desk.zoho.eu (EU), desk.zoho.in (India), desk.zoho.com.au (Australia), desk.zoho.com.cn (China), desk.zoho.jp (Japan), and desk.zoho.ca (Canada). The api console URL follows the same TLD pattern. Calls to the wrong region return 401 even with valid tokens.
Step 1: Register a Self-Client App in Zoho API Console
Open api-console.zoho.com (substitute your regional TLD if needed) and sign in with the integration account. Click Add Client, then choose Self Client from the list. This option is purpose-built for server-to-server integrations that do not require interactive user consent at runtime.
The console will display a Client ID and Client Secret. These are the long-term credentials your integration uses to mint access tokens. Copy both values into your secrets manager immediately. Treat them like database passwords: never commit them to git, never log them, and rotate them on a defined cadence.
Next, click the Generate Code tab inside the Self Client. Enter the scopes your integration needs in comma-separated form. Common scopes for a typical integration are Desk.tickets.ALL,Desk.contacts.ALL,Desk.basic.READ. Add Desk.settings.ALL if you plan to manage departments, layouts, or webhooks programmatically. Set the time duration to 10 minutes - the generated code expires quickly and is intended to be exchanged for a refresh token immediately.
When you click Generate, the console returns a temporary authorization code. Copy that string and move directly to Step 2. The code becomes invalid once it has been used or after the time window passes, whichever comes first.
Step 2: Generate OAuth Tokens for Server-to-Server Calls
Exchange the authorization code for a refresh token and access token by POSTing to the Zoho accounts endpoint for your region. The accounts host follows the same regional pattern as the desk URL: accounts.zoho.com for US, accounts.zoho.eu for EU, and so on.
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_AUTH_CODE"
The response includes access_token, refresh_token, expires_in, and token_type. The refresh token is long-lived and should be stored in your secrets manager alongside the client credentials. The access token expires after the period in expires_in (typically one hour) and must be regenerated using the refresh token.
Here is the same flow in Python:
import requests
resp = requests.post(
"https://accounts.zoho.com/oauth/v2/token",
data={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": AUTH_CODE,
},
timeout=30,
)
tokens = resp.json()
refresh_token = tokens["refresh_token"]
access_token = tokens["access_token"]
To refresh an expired access token later, POST to the same endpoint with grant_type=refresh_token and the stored refresh token:
curl -X POST https://accounts.zoho.com/oauth/v2/token \
-d "grant_type=refresh_token" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN"
Cache the access token in memory or a short-lived store and only refresh when it has fewer than five minutes of life remaining. This avoids unnecessary trips to the accounts endpoint on every API call.
Step 3: List Tickets via the Zoho Desk API
With a fresh access token in hand, list tickets by issuing a GET request to the tickets endpoint. The Authorization header carries the token in Zoho-oauthtoken ACCESS_TOKEN format, and the orgId header identifies the organization.
curl -X GET "https://desk.zoho.com/api/v1/tickets?limit=10" \
-H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN" \
-H "orgId: YOUR_ORG_ID"
The response is a JSON object with a data array containing ticket objects. Each ticket includes fields like id, ticketNumber, subject, status, priority, contactId, departmentId, and createdTime. The default page size is 50 and the maximum is 100. Use the from parameter to offset into deeper pages.
In Python:
headers = {
"Authorization": f"Zoho-oauthtoken {access_token}",
"orgId": ORG_ID,
}
resp = requests.get(
"https://desk.zoho.com/api/v1/tickets",
headers=headers,
params={"limit": 100, "from": 0},
timeout=30,
)
tickets = resp.json().get("data", [])
Filter the result set with query parameters. Useful filters include status, priority, assigneeId, departmentId, channel, and createdTimeRange. The include parameter can pull related records like contacts or assignee in the same response and reduce round trips. To pull a single ticket by ID, append the ID to the path: /api/v1/tickets/TICKET_ID.
Step 4: Create a Ticket Programmatically
Posting a new ticket requires a JSON body with at minimum a subject, a contact reference, and a department. Send a POST request with the same Authorization and orgId headers, plus a Content-Type header of application/json.
curl -X POST https://desk.zoho.com/api/v1/tickets \
-H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN" \
-H "orgId: YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"subject": "Login fails on mobile app",
"departmentId": "1234567000000123456",
"contactId": "1234567000000234567",
"description": "User reports repeated 401 errors on iOS 17.",
"priority": "High",
"channel": "API"
}'
The response returns the full ticket object including the system-assigned id and ticketNumber. Store the id if you intend to reference the ticket later for updates, comments, or status transitions. Common follow-up calls include POSTing to /tickets/TICKET_ID/comments to add internal notes and PATCHing to /tickets/TICKET_ID to change status, priority, or assignee.
A Node.js create ticket example using the native fetch API:
const response = await fetch("https://desk.zoho.com/api/v1/tickets", {
method: "POST",
headers: {
"Authorization": `Zoho-oauthtoken ${accessToken}`,
"orgId": ORG_ID,
"Content-Type": "application/json",
},
body: JSON.stringify({
subject: "Login fails on mobile app",
departmentId: DEPARTMENT_ID,
contactId: CONTACT_ID,
description: "User reports repeated 401 errors on iOS 17.",
priority: "High",
channel: "API",
}),
});
const ticket = await response.json();
If the contact does not yet exist, you can either create the contact first via the contacts endpoint and pass the returned ID, or use the contact object inline with email, firstName, and lastName fields and Zoho Desk will create or match the contact automatically.
Step 5: Manage Contacts and Accounts via API
Contacts represent the end users who submit tickets. The contacts endpoint follows the same conventions as tickets: GET, POST, PATCH, and DELETE on /api/v1/contacts and /api/v1/contacts/CONTACT_ID. Accounts represent the company a contact belongs to, exposed at /api/v1/accounts.
Create a contact with a POST:
curl -X POST https://desk.zoho.com/api/v1/contacts \
-H "Authorization: Zoho-oauthtoken YOUR_ACCESS_TOKEN" \
-H "orgId: YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"lastName": "Doe",
"firstName": "Jane",
"email": "[email protected]",
"phone": "+1-555-0100",
"accountId": "1234567000000345678"
}'
Search for contacts by email when you need to deduplicate before creating: GET /api/v1/contacts/[email protected]. The search endpoint accepts multiple field filters and returns matches in the same paginated data array shape.
Linking contacts to accounts is the typical pattern for B2B helpdesks. Create the account first, capture its id, and pass it as accountId when creating contacts. Accounts also support custom fields, which appear in the response object under their api names. Define custom fields in the Zoho Desk admin UI under Setup, then Customization, then Layouts before referencing them in api calls.
Step 6: Configure Webhooks for Ticket Events
Webhooks let Zoho Desk push events to your service in near real time instead of polling. Supported ticket events include ticket.created, ticket.updated, and ticket.closed. Webhooks can also be configured against contact, task, and call events depending on your plan tier.
Create a webhook through the Zoho Desk UI under Setup, then Developer Space, then Webhooks. The form asks for a name, the events to subscribe to, the destination URL, and an optional authorization header value that Zoho will include on every callback. Use the authorization header to validate that incoming requests are actually from Zoho Desk.
Webhook payloads are JSON and include the event type, the organization ID, and the affected resource ID. They do not always include the full resource body - design your webhook handler to fetch the latest ticket state from the api when the payload arrives so you operate on canonical data rather than a possibly stale snapshot.
A minimal webhook handler in Python using FastAPI:
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
EXPECTED_AUTH = "Bearer your-shared-secret"
@app.post("/zoho-desk/webhook")
async def webhook(req: Request):
if req.headers.get("Authorization") != EXPECTED_AUTH:
raise HTTPException(status_code=401)
payload = await req.json()
event = payload.get("eventType")
ticket_id = payload.get("ticketId") or payload.get("id")
# enqueue background job to fetch and process the ticket
return {"received": True}
Make webhook handlers idempotent. The same event can be delivered more than once, especially during retries, and your downstream side effects need to tolerate duplicates without creating phantom records.
Common HTTP Errors and Fixes
| Status | Meaning | Likely Cause | Fix |
|---|---|---|---|
| 400 | Bad Request | Missing required field or invalid JSON body | Validate payload against the documented schema; check for typos in field names |
| 401 | Unauthorized | Expired access token or wrong region URL | Refresh the access token; verify base URL TLD matches your data center |
| 403 | Forbidden | OAuth scope insufficient or user lacks permission | Re-issue tokens with broader scopes; confirm integration user has role access |
| 404 | Not Found | Wrong resource ID, wrong department, or wrong orgId | Confirm the orgId header value; verify the ID exists in the targeted org |
| 422 | Unprocessable Entity | Validation failed on a field constraint | Inspect the response body for the specific field error |
| 429 | Too Many Requests | Rate limit hit on the API or accounts endpoint | Back off, retry with exponential delay, batch requests where possible |
| 500 | Server Error | Transient platform issue | Retry with backoff; if persistent, check Zoho status page |
The 401 response is the most common error for new integrations and almost always reduces to one of three causes: the access token expired, the authorization header format is wrong (it must read Zoho-oauthtoken TOKEN not Bearer TOKEN), or the request is going to the wrong regional URL.
Production Patterns: Refresh Tokens, Pagination, Rate Limits
Refresh token handling is the single most important production concern. Refresh tokens do not expire on a fixed schedule but they can be revoked by the user, by an admin, or by Zoho if abuse is detected. Build a fallback path that detects refresh failure and surfaces a clear alert to the integration owner so the consent flow can be re-run before downstream systems start failing silently.
Pagination on list endpoints uses the from and limit parameters. The limit maximum is 100. Loop until the returned data array is empty or shorter than limit. Avoid sorting by createdTime and using time-window filters together with pagination - it can create gaps if records are inserted with timestamps inside an already-paginated window. Prefer sorting by id for stable cursors.
Rate limits apply per organization and per api client. Implement exponential backoff with jitter on every 429 response. A simple decorator pattern in Python keeps this clean:
import time, random
def with_retry(max_attempts=5):
def deco(fn):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
resp = fn(*args, **kwargs)
if resp.status_code != 429:
return resp
wait = (2 ** attempt) + random.random()
time.sleep(wait)
return resp
return wrapper
return deco
Cache reference data that does not change frequently - departments, layouts, custom fields, and agents. Refreshing those once per hour or once per day instead of on every request reduces api consumption substantially and protects you against rate limit spikes during traffic peaks.
Log every api call with the request ID returned in the X-Request-Id response header. When you open a support ticket with Zoho about an unexpected response, the request ID is the fastest way for their team to find your call in their logs.
Finally, separate environments cleanly. Maintain distinct OAuth clients for development, staging, and production, each pointing at its own Zoho Desk org or sandbox. Sharing one client across environments leads to credential leaks and webhook collisions that are painful to untangle. For broader market context on helpdesk integrations, see our best customer support software 2026 review.
Want to learn more about Zoho Desk?
Frequently Asked Questions
Does Zoho Desk have an API?
Yes. Zoho Desk exposes a full REST api at /api/v1/ under regional base URLs like desk.zoho.com and desk.zoho.eu. The api covers tickets, contacts, accounts, departments, agents, comments, threads, articles, and tasks, plus webhooks for ticket lifecycle events. Authentication is OAuth 2.0 and most calls require an orgId header. Free and Express tiers expose the api with reduced coverage; Standard at $14 per agent per month annual is the minimum tier we recommend for production integrations.
Is Zoho Desk free to use?
Zoho Desk has a Free tier that supports up to 3 agents, email ticketing, a basic help center, and limited api access. The Free tier is suitable for very small teams or proof-of-concept integrations but constrains automation, social channels, and api endpoint coverage. Paid plans start at Express ($7 per agent per month annual) and scale up through Standard, Professional, and Enterprise as feature and capacity needs grow.
Is Zoho Desk a CRM tool?
No. Zoho Desk is help-desk and customer-support software focused on ticketing, knowledge bases, and agent workflows. The Zoho suite includes a separate product, Zoho CRM, that handles sales pipelines, leads, and deals. Zoho Desk and Zoho CRM are tightly integrated - contacts and accounts can sync between them, and ticket context appears inside CRM records - but they are distinct applications with separate apis and pricing.
How do I authenticate to the Zoho Desk API?
Authentication uses OAuth 2.0. For server-to-server integrations, register a Self-Client app in the Zoho api console at api-console.zoho.com, generate an authorization code with the scopes you need (commonly Desk.tickets.ALL,Desk.contacts.ALL,Desk.basic.READ), and exchange that code for a refresh token by POSTing to https://accounts.zoho.com/oauth/v2/token. Store the refresh token in your secrets manager and exchange it for short-lived access tokens as needed. Include each access token in the Authorization: Zoho-oauthtoken TOKEN header on api requests, alongside an orgId header identifying your organization.
Related Reading
- Zoho Desk tool page
- Best customer support software 2026
- Best marketing automation tools 2026
- AI social media automation
Related Guides
External Resources
Start a Zoho Desk trial See Zoho Desk pricing Official Zoho Desk API documentationRelated Guides
- Zoho AI Guide: Zia Features, Pricing, and Use Cases 2026
- Zoho Connect Setup Guide: Build a Team Network 2026
- Zoho CRM Pricing Guide: All Tiers & Costs Compared
- Zoho Deluge Scripting Guide: Automate Creator Apps
- Zoho Desk Setup Guide 2026: Build Your Help Center
- Zoho Desk Zia AI: Auto-Tagging and Sentiment Guide
- Zoho Forms Tutorial: Conditional Logic Setup Guide