hieuvnlabs logo
Hiusvu

How To Search Tickets with the Zoho Desk API

Published on September 8, 2026

How To Search Tickets with the Zoho Desk API

Introduction

Zoho Desk ships a fairly complete REST API for working with tickets. Among its endpoints, Search Tickets lets you filter on several criteria at once: department, status, assignee and, most usefully, time ranges such as created time, modified time or due date (dueDate).

A common real-world need: automatically find tickets that are due within a certain window so you can remind the agent, push a notification to Cliq or Slack, or update the status. In this guide we will:

  • Look at the Search Tickets endpoint and its most important parameters.
  • Handle time zones correctly when passing a time range (the API always uses UTC).
  • Write a Deluge custom function in Zoho Desk that finds tickets by dueDateRange.
  • Test with curl and handle pagination.

Prerequisites

  • A Zoho Desk organisation and permission to create custom functions (Setup → Developer Space → Functions).
  • A Connection in Zoho Desk with the ticket read scopes (Desk.tickets.READ and Desk.search.READ). This guide uses connection_name as the placeholder.
  • The ID of the department (departmentId) you want to filter on. You can get it with GET /api/v1/departments.

Step 1 — Understanding the Search Tickets Endpoint

The endpoint looks like this:

GET https://desk.zoho.com/api/v1/tickets/search

If your account lives in another data centre (EU, IN, AU, etc.), change the domain accordingly, for example desk.zoho.eu or desk.zoho.in.

Every request needs two headers:

  • Authorization: Zoho-oauthtoken <access_token>: the OAuth token. When you use a Connection from Deluge, Zoho adds this header for you.
  • orgId: <org_id>: your Zoho Desk organisation ID. Find it under Setup → Developer Space → API, or call GET /api/v1/organizations.

The most useful query parameters:

ParameterMeaning
limitTickets per page, maximum 100
fromStart index (0-based), used for pagination
departmentIdFilter by department
statusFilter by status, e.g. Open, On Hold, Closed
assigneeAgent ID
priorityHigh, Medium, Low
channelChannel the ticket came from: Email, Phone, Web, etc.
_allFull-text search across several fields
createdTimeRangeCreated-time window, formatted as start,end
modifiedTimeRangeLast-modified window
dueDateRangeDue date window, formatted as start,end
sortBySort field, e.g. dueDate or -createdTime (a leading - means descending)

Parameters ending in Range take two timestamps separated by a comma, in ISO 8601 format and always in UTC, for example:

dueDateRange=2025-06-26T18:59:00.000Z,2025-06-26T19:01:00.000Z

You can combine as many parameters as you like; they are ANDed together.

Step 2 — Getting the Time Zone Right

This is where most mistakes happen. Zoho Desk stores and compares times in UTC, whereas zoho.currenttime in Deluge returns the time in your organisation's time zone (UTC+7 for Vietnam).

If you take zoho.currenttime and format it straight into a string ending in Z, you are labelling local time as UTC and every result will be off by seven hours. The fix is to subtract the offset before formatting:

localTime = zoho.currenttime;
utcTime = localTime.toTime().subtractHours(7);
isoZ = utcTime.toString("yyyy-MM-dd'T'HH:mm:ss'.000Z'");
info isoZ;

For example, if it is currently 2025-06-27 02:00:00 in Vietnam, isoZ becomes 2025-06-26T19:00:00.000Z. If your organisation uses a different time zone, replace 7 with the matching offset.

Note: The literal characters T and .000Z are wrapped in single quotes in the format string so Deluge does not interpret them as format codes.

Step 3 — Writing the Custom Function in Deluge

Go to Setup → Developer Space → Functions → New Function. Choose the Tickets module, give the function a name such as filter_ticket_rangeduedate, and paste in the code below.

This function finds tickets in one department that are due within the next 60 minutes, counted from the moment it runs:

// 1. Work out the time window in UTC
localTime = zoho.currenttime;
utcNow = localTime.toTime().subtractHours(7);
utcEnd = utcNow.addMinutes(60);

isoZStart = utcNow.toString("yyyy-MM-dd'T'HH:mm:ss'.000Z'");
isoZEnd = utcEnd.toString("yyyy-MM-dd'T'HH:mm:ss'.000Z'");

// 2. Build the search URL
searchURL = "https://desk.zoho.com/api/v1/tickets/search" +
            "?limit=100" +
            "&from=0" +
            "&departmentId=XXXXXXXXXXXX" +
            "&dueDateRange=" + isoZStart + "," + isoZEnd +
            "&sortBy=dueDate";

// 3. Call the API through the Connection
response = invokeUrl
[
    url : searchURL
    type : GET
    connection : "connection_name"
];

// 4. Process the result
tickets = response.get("data");
if (tickets == null)
{
    info "No tickets are due in the next 60 minutes.";
    return;
}

info "Found " + tickets.size() + " ticket(s):";
for each ticket in tickets
{
    info "#" + ticket.get("ticketNumber")
        + " | " + ticket.get("subject")
        + " | Due: " + ticket.get("dueDate")
        + " | Status: " + ticket.get("status");
}

Replace XXXXXXXXXXXX with the real departmentId and connection_name with your Connection's name. Click Save, then Execute to try it; the output shows up in the log tab.

If you want a fixed window instead of a dynamic one, just replace the dueDateRange string with two explicit timestamps:

"&dueDateRange=2025-06-26T18:59:00.000Z,2025-06-26T19:01:00.000Z"

Step 4 — Reading the Response

When there are matches, the API returns JSON with a data array, each element being a trimmed-down ticket:

{
  "data": [
    {
      "id": "1892000000042032",
      "ticketNumber": "1045",
      "subject": "Unable to log in to the system",
      "status": "Open",
      "priority": "High",
      "dueDate": "2025-06-26T19:00:00.000Z",
      "createdTime": "2025-06-25T08:12:44.000Z",
      "departmentId": "1892000000006907",
      "assigneeId": "1892000000056001",
      "channel": "Email",
      "webUrl": "https://desk.zoho.com/support/yourorg/ShowHomePage.do#Cases/dv/1892000000042032"
    }
  ]
}

A few things to keep in mind:

  • When no ticket matches, the API returns HTTP 204 No Content with an empty body. In Deluge, response.get("data") is then null, so always check it as in Step 3.
  • dueDate is returned in UTC. To display local time, add the offset back.
  • webUrl opens the ticket directly in the Zoho Desk UI, which is handy for notifications.

Step 5 — Testing With curl

For a quick test outside Deluge, call the API directly with an OAuth access token:

curl -G "https://desk.zoho.com/api/v1/tickets/search" \
  -H "Authorization: Zoho-oauthtoken 1000.xxxxxxxx.yyyyyyyy" \
  -H "orgId: 12345678" \
  --data-urlencode "limit=100" \
  --data-urlencode "from=0" \
  --data-urlencode "departmentId=XXXXXXXXXXXX" \
  --data-urlencode "dueDateRange=2025-06-26T18:59:00.000Z,2025-06-26T19:01:00.000Z" \
  --data-urlencode "sortBy=dueDate"

--data-urlencode takes care of encoding the commas and colons in the time range.

Add more conditions, for example only open, high-priority tickets:

  --data-urlencode "status=Open" \
  --data-urlencode "priority=High"

Step 6 — Paginating Past 100 Tickets

Each call returns at most 100 tickets. To fetch everything, increase from by limit on each iteration until the API returns nothing:

allTickets = List();
fromIndex = 0;
pageSize = 100;
hasMore = true;

while (hasMore)
{
    searchURL = "https://desk.zoho.com/api/v1/tickets/search" +
                "?limit=" + pageSize +
                "&from=" + fromIndex +
                "&departmentId=XXXXXXXXXXXX" +
                "&dueDateRange=" + isoZStart + "," + isoZEnd;

    response = invokeUrl
    [
        url : searchURL
        type : GET
        connection : "connection_name"
    ];

    page = response.get("data");
    if (page == null || page.size() == 0)
    {
        hasMore = false;
    }
    else
    {
        allTickets.addAll(page);
        fromIndex = fromIndex + pageSize;
        if (page.size() < pageSize)
        {
            hasMore = false;
        }
    }
}

info "Total tickets: " + allTickets.size();

Note: Zoho Desk limits the number of API calls according to your plan. For scheduled jobs, keep the time window narrow to reduce the number of pages you need to fetch.

Step 7 — Scheduling It

Once the function runs correctly, attach it to a Schedule (Setup → Automation → Schedules) to run every hour. Combined with sendmail (or a Cliq/Slack webhook), it can warn the assigned agent before a ticket goes overdue:

for each ticket in tickets
{
    sendmail
    [
        from : zoho.adminuserid
        to : "support-team@your_domain"
        subject : "Ticket #" + ticket.get("ticketNumber") + " is due soon"
        message : "Ticket: " + ticket.get("subject") + "<br>Due: " + ticket.get("dueDate") + "<br><a href='" + ticket.get("webUrl") + "'>Open ticket</a>"
    ]
}

Conclusion

You now know how to use the Zoho Desk Search Tickets API to filter tickets by department and dueDate range, handle the offset between local time and UTC, write the logic as a Deluge custom function, paginate through results and run it on a schedule. With the same approach you can swap dueDateRange for createdTimeRange or modifiedTimeRange to build other reports and alerts for your support team.