Files
sim/apps/docs/content/docs/en/integrations/zoho_desk.mdx
T
Waleed 292e59f693 fix(docs): stop publishing unsettable params, fix comment blanking (#7169)
* fix(docs): stop publishing unsettable params, fix comment blanking

generate-docs never read visibility, so every tool param appeared in the
public Input table -- including params marked visibility:'hidden', which are
shown to neither the user nor the LLM. Several are credential-shaped
(idToken, instanceUrl, apiToken, cloudId), so the docs told integrators they
could set values they cannot reach.

A hidden param is now dropped only when the block declares no subBlock for
it, matched on id or canonicalParamId -- a param can be hidden on the tool
because the block injects it while the block still renders it as a required
field the user types.

blankStringsAndComments kept the first and last character of every match.
That is right for a quoted string, where both are delimiters, but for a '//'
comment the last character is arbitrary source text, so a commented-out
'//   options: [' left an unbalanced bracket that derailed the subBlock
scan. Parsing now throws rather than silently reporting that a block
exposes nothing, since that fallback was the destructive one.

Also corrects the LinkedIn w_member_social consent-screen description, which
read 'Access LinkedIn profile' for a scope that posts on the user's behalf.

* fix(docs): keep hidden params the block mapper supplies

The carve-out only recognized an identity match between a subBlock id and a
tool param, so a block that renames or assembles the value in
tools.config.params was invisible to it -- and the row was dropped even
though the user types it.

Cal.com's attendee (required) is assembled from attendeeName/attendeeEmail/
attendeeTimeZone; JSM's workspaceId comes from assetWorkspaceId; Textract
writes parameters.file from a field whose canonicalParamId is 'document',
which left the Mistral PDF Parser documenting zero inputs.

Collects params written by any accumulator identifier, not just 'result',
since the two real mappers use different names. Object keys are collected
without proving they are top-level, so a nested key can produce a false
keep -- one hard-to-set row is better than hiding a required input.

* fix(tools): reject values that cannot be a path segment

toGuardedString coerced with String(value), so an object reached the wire as
%5Bobject%20Object%5D and a boolean as 'true' -- a doomed request instead of
a clean error, on 44 live call sites. Accepts string, bigint, and finite
non-exponential numbers; everything else throws a named error.

Rejects a number whose decimal text is a rewrite rather than the caller's
value: 1e21 stringifies to '1e+21', and an integer past 2^53 has already
lost digits. A snowflake cannot be repaired here at all -- JSON.parse
destroys it before this runs -- so the doc now says it must arrive as a
string, and cites Box folderId (root = 0) instead.

Corrects the claim that the parser removes only an exact '.' or '..'; the
spec defines 11 removable spellings. The guards are sufficient because
encodeURIComponent escapes '%', not because the others cannot occur.

* test(oauth): pin the LinkedIn write-scope description

Nothing guarded the consent-screen text: utils.test.ts covered only the
Bitbucket and Reddit overrides, and the modal test stubs
getScopeDescription to identity, so a regression to a read-only label for a
posting scope would pass silently.

* fix(docs): stop a comment hijacking the id scan, unhang a Firecrawl reference

The depth-1 walk copied from the raw source at indices where the blanked
copy was at depth 1, so 'id:' inside a string value or a comment landed in
the scanned text and won the first match. With the keep-bias that now means
a phantom id can retain a param the block never exposes. Matching runs on
the blanked text and reads the literal back through a source-index map. No
block in the repo trips this today -- verified across all 305 -- so this is
a latent fix.

Removing the unsettable scrapeOptions row left five Firecrawl Search output
descriptions referencing a name that no longer appears on the page. They
now describe the response condition instead. Pointing them at 'formats' was
not an option: that subBlock is conditioned on scrape/parse/batch_scrape and
the search tool declares no such param, so it would have swapped one
dangling reference for another.

* refactor(tools): drop the unused guard API from this PR

safeUrlPath, safeOpaqueUrlSegment and SafeUrlPathOptions had zero call
sites -- 469 lines of unused API in a docs-generator change, including an
allowEmptySegments flag whose own TSDoc documents a host-takeover footgun
('//evil.com' under new URL(relative, base)). They belong with the ~693
traversal call sites that use them, where they can be reviewed against real
usage.

What stays is the part with 44 live consumers: safeUrlPathSegment now
accepts number and bigint. Staging already rejected every non-string, so
this only widens acceptance -- a differential over 87 real call-site values
shows 87 identical, 0 differing.

Also: encodeURIComponent throws an unnamed URIError on a lone surrogate,
which JSON.parse accepts, so a truncated emoji lost the param name the file
claims as its invariant. And the exponential rejection told tiny values like
1e-7 they were 'too large' when they round-trip exactly; the rejection is
right -- a path segment should not rewrite 0.0000001 into other text -- but
the stated ground was not.

* fix(docs): abort before writing on a parse failure, unhang three descriptions

The guard's own TSDoc said to fail rather than guess, because the fallback
strips every hidden param from a page. The caller did the opposite: it
caught, recorded, and continued with an empty id set, and the non-zero exit
came after every page was already written. A developer who reran, saw red,
and missed the scrollback could commit a stripped page. Parse failures are
now detected in a dry pass before anything is emitted.

The zero-id guard also only fired when the array held a literal '{', so a
subBlocks built by a helper call was silently empty, while a subBlocks whose
literals only spread ({ ...sb, required: true }) hard-failed the build. It
now fires on the destructive case alone -- reporting an unreadable array
while leaving legitimate opaque spreads, which 23 blocks rely on, untouched.

Three descriptions referenced things the reader can no longer see: Dataverse
mandated base64 after its base64 row was removed, Vanta's mimeType described
itself as useful only on that removed path, and five Drive actions told the
reader to fetch a next page with no input left to accept the token.

* chore(tinyfish): use a white block background

Matches the dominant convention (97 blocks use #FFFFFF). Regenerates the
docs page, the tool metadata, and the deployment catalog, which each carried
the previous value.

* fix(docs): correct Drive/Firecrawl/Vanta output and param descriptions

Google Drive nextPageToken: the discovery doc says the field is *absent*
at the end of the list, not empty. Say absent, and name the resource
(files/comments/permissions/revisions) per tool.

Firecrawl search outputs: restore the per-format gate the v2 OpenAPI
states ("HTML content if requested in formats"), and add that Search
exposes no input for those formats.

Vanta mimeType: the route resolves a content type from storage on every
path, so the param is never read. Say so instead of describing it as a
fallback.

Dataverse: describe the request as sending the bytes as the raw body.

* fix(docs): correct the Vanta mimeType and Firecrawl reachability wording

Vanta: the base64 branch (route.ts:100) reads params.mimeType as its only
content-type source, so "not currently applied" was wrong. Say it applies
there and is a fallback on the File branch.

Firecrawl: scrapeOptions is declared in the block's inputs map with no
subBlock, so it is reachable by a direct tool call. "Exposes no visible
input" rather than "exposes no input".

* fix(docs): never abort the generator on an unreadable subBlocks array

An unreadable `subBlocks` value used to throw, and with no spread base to
fall back on the failure was fatal: the pre-scan recorded it and
`generateAllBlockDocs` returned false, so `main` exited 1 and nothing was
written at all. Nine shipped blocks already use the non-literal form and
are saved only because they happen to spread a base — the first block
authored as `subBlocks: myFields` without one would brick `generate-docs`
and `docs:check` for the whole repository.

The author's reason for aborting was sound: an empty `userSettableParamIds`
is indistinguishable from "nothing is settable", which strips every hidden
param and publishes a wrong page. So the fix is not to treat the failure as
empty — it is to represent UNKNOWN distinctly. `extractBlockSuppliedParamIds`
now returns `{ ids, mapperIds, parseError }` with `ids: null` for UNKNOWN,
that `null` flows through `BlockConfig.userSettableParamIds`, `getToolInfo`
and `extractToolInfo`, and the filter site skips filtering entirely when it
sees it — restoring the pre-filter behaviour for that one block instead of
killing the run. `getToolInfo`'s default is `null` for the same reason: `[]`
as a default silently meant "strip everything".

The mapper scan now runs before the subBlocks scan, so a spread-inheriting
block keeps its mapper's renames when only the subBlocks scan fails. With
nothing left that can record a fatal, the dry pre-scan and its reporting
are removed.

Also fixes a silent blind spot in the mapper scan: both key regexes require
a literal `:`, so a mapper returning a shorthand property (`{ file }`) or
writing a computed key (`result['file'] = …`) dropped a real user input from
the docs with no warning. Shorthand names are read from the depth-1 comma
segments of brace-matched regions, which keeps call argument lists from
contributing names.

Verified byte-identical output: `scripts/generate-docs.ts` and
`tool-metadata:generate` reproduce all 302 generated files unchanged, the
credential-shaped hidden params stay stripped, and `check:audits` passes.

* fix(docs): name the input that gates Firecrawl search scrape output

The previous wording ended each description with "for which the Search
operation exposes no visible input", a relative clause that attaches
ambiguously and never tells the reader what controls the field. Name
scrapeOptions and note that it is hidden.

* fix(docs): say the Vanta mimeType is ignored for File-input uploads

Every return path of downloadServableFileFromStorage yields a non-empty
contentType (a literal, getMimeTypeFromExtension's GENERIC_MIME_TYPE
fallback, or resolveServableDocBytes' constants/getContentType), so
resolved.contentType always wins at route.ts:79-81 and params.mimeType is
unreachable on that branch. It is not a fallback; it is ignored.

* fix(docs): note the hidden inputs that gate Pulse html and figures output

extractFigure and returnHtml are visibility: 'hidden' with no subBlock, and
parser.ts:135-144 only forwards them when defined, so neither output can be
produced today. Say so on the output rows rather than deleting them, since
removing an output field would break saved block references.

chunks is left alone: chunking/chunkSize are user-only with real subBlocks.

* fix(docs): report a spread-only subBlocks array as unknown, not empty

extractUserSettableParamIds answered [] for a subBlocks array whose every
element spreads a fields array it cannot follow (NotionV2Block's
`[...NotionBlock.subBlocks, ...getTrigger(x).subBlocks]`). [] asserts the
block supplies nothing, so the hidden-param filter stripped every hidden
param from every tool the block owns - silently, with no parseError and so
no warning. That is the exact false-drop the null UNKNOWN state exists to
prevent.

Return null in that case and propagate it: extractBlockSuppliedParamIds no
longer folds it into [], and the block pass no longer collapses it with
`supplied.ids ?? []`. A config-level spread base still narrows the filter to
its readable fields plus the mapper's renames; with no base the filter is
switched off. An array with at least one inline id, a genuinely empty array,
and the existing throw/warn paths are unchanged - all 8 warned blocks warn
identically and every generated page is byte-identical.

Also pin the hidden-param filter on extractToolInfo's source-parsing path,
which had no coverage at all: deleting it outright left the suite green.

* fix(daytona): stop the lifecycle tools crashing on a non-string sandboxId

start/stop/delete echo sandboxId back as the output id when the API returns
no body, via params.sandboxId.trim() inside transformResponse - after the
request has already gone out. sandboxId is declared type: 'string' but
arrives unvalidated, and now that safeUrlPathSegment accepts a numeric id a
number builds a URL, sends the DELETE/START/STOP, and only then throws an
unnamed TypeError. Both the old and new behaviour fail, so this is not a
regression of a working workflow, but for delete_sandbox the side effect is
irreversible and the caller cannot tell what happened.

Fixed with a shared resolveSandboxId in utils.ts rather than a coercion at
each of the three sites: utils.ts already owns every sandbox-id helper, the
three tools already import from it, and the reasoning belongs in one place.
The encoded value cannot be reused - it is percent-encoded and would be
wrong as an output id. Behaviour for a string is unchanged.

* docs(url-path): drop the false claim that widening restores prior behaviour

The module TSDoc said the number/bigint widening fixed 'a regression for the
call sites whose pre-guard form was a bare ${params.id} template that
stringified a number fine'. It did not. Every pre-guard form in a42299066d
used .trim() (`/v13/deployments/${params.deploymentId.trim()}`,
`sandboxId?.trim()`), so a numeric id threw there too - no importer has ever
accepted one. The cited examples were also wrong: only Vercel and Daytona
import this module, and neither Box nor X does.

Replaced with the real motivation - params are declared type: 'string' but
nothing enforces it before the guard, and the old coercion-to-'' turned a
supplied numeric id into a misleading 'is required'. Two test comments made
the same claim ('still stringifies', 'replaced bare ${params.id} templates')
and are corrected; no assertion is weakened.

* fix(docs): correct output rows that cite inputs Sim does not send

mistral_parse: the PR removed the includeImageBase64 input row but left the
image_base64 output citing include_image_base64=true, so the page referenced
an input it no longer documents. includeImageBase64 is visibility: 'hidden'
with no subBlock, mapper or canonicalParamId, so it is annotated the same way
Pulse's html and figures were.

The sibling rows are a stronger defect: table_format, extract_header and
extract_footer appear nowhere in the repo - not as tool params, not in the
request body parser.ts builds - so tables/header/footer cited options Sim
never sends. Worded accordingly rather than as hidden inputs.

pulse structured_output cited 'if schema was provided', but there is no
schema or structuredOutput param in the tool, in pulseParseInputSchema, or in
the outgoing body, so the field is always null.

No output field is deleted - removing one changes the block's output schema
and could break saved workflow references.

* style: wrap long description literals to satisfy biome

Formatting only — regenerating both artifacts produces a byte-identical
tree, so no description text changed.

* fix(docs): cite the real Mistral options behind tables, header and footer

The previous wording was self-contradictory on tables: it described
placeholder-referenced table objects and then asserted the list is empty.
Mistral's OCR API does expose table_format, extract_header and
extract_footer. table_format defaults to inline markdown, so the separate
tables list stays empty; extract_header and extract_footer default to
false, so neither field is returned. Sim sets none of the three.

Name the option and its default in each description instead of asserting
an outcome the request body alone does not establish.
2026-08-27 19:50:03 -07:00

516 lines
25 KiB
Plaintext

---
title: Zoho Desk
description: Manage Zoho Desk tickets, comments, threads, and contacts
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="zoho_desk"
color="#FFFFFF"
/>
{/* MANUAL-CONTENT-START:intro */}
[Zoho Desk](https://www.zoho.com/desk/) is Zoho's customer support help desk. Support teams use it to receive tickets from email, web forms, chat, phone, and social channels, route them to the right department and agent, and track every customer conversation through to resolution.
With the Sim Zoho Desk integration, you can:
- **Read and filter tickets**: List tickets across an organization filtered by department, status, or priority, or fetch a single ticket by ID with its related contact, assignee, and department.
- **Update tickets**: Change subject, status, priority, assignee, department, category, due date, and custom fields — useful for AI triage that classifies an incoming ticket and writes the result back.
- **Work with conversations**: List and read ticket threads (the customer-facing email/chat exchange) and comments (internal agent notes), then add your own comment as public or private.
- **Look up contacts**: Retrieve the contact behind a ticket to enrich it with data from your CRM or knowledge base.
- **Download attachments**: Pull an attachment from a thread or comment into a Sim file you can pass to downstream blocks.
- **Trigger on events**: Start a workflow when a ticket, comment, thread, contact, agent, task, or article changes in Zoho Desk.
**How it works in Sim:**
Add a Zoho Desk block to your workflow, connect your Zoho account, and pick the Organization (portal) to work in — Sim loads the list for you from the connected account. Choose an operation and fill in its parameters; the block calls the Zoho Desk API and returns structured data for downstream blocks. For comment and thread bodies, Sim adds a derived plain-text `contentText` field alongside Zoho's raw HTML `content`, so an AI agent can read the message without HTML markup.
To trigger on Zoho Desk activity instead, use the block's trigger mode. Sim creates the webhook subscription in Zoho Desk for you and removes it automatically when the workflow is undeployed.
**Requirements and limitations**
> Zoho Desk webhooks require a Zoho Desk edition of **Professional or higher** — Free and Standard plans cannot create webhook subscriptions, so the trigger will fail to deploy on those plans.
>
> Connecting a Zoho account with **OAuth** — which the trigger requires — works only for the **US data center** (`accounts.zoho.com`). To use Zoho Desk blocks from the EU, India, or Australia data centers, connect a [Self Client](/integrations/zoho-desk-service-account) instead and set its data center. The Japan, Canada, Saudi Arabia, China, and UK data centers are not supported by either path.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Read and update Zoho Desk tickets, manage comments and threads, look up contacts, and download attachments. Can also trigger workflows from Zoho Desk webhook events.
## Actions
### Zoho Desk List Tickets
List tickets from a Zoho Desk organization with optional filters. Returns a list projection: description, resolution, statusType and classification are only available from Get Ticket.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `from` | number | No | Pagination start index \(0-based\) |
| `limit` | number | No | Number of tickets to return \(1-100\) |
| `departmentIds` | string | No | Filter by department ID \(comma-separated for multiple\) |
| `status` | string | No | Filter by status, including custom statuses. Comma-separate to match multiple \(e.g. "Open,On Hold"\) |
| `priority` | string | No | Filter by priority. Comma-separate to match multiple \(e.g. "High,Urgent"\) |
| `assignee` | string | No | Filter by assignee: an agent ID, or "Unassigned". Comma-separate to match multiple. |
| `channel` | string | No | Filter by origin channel, spelled as your portal spells it. Comma-separate to match multiple. |
| `receivedInDays` | number | No | Only tickets whose last customer response was within the last 15, 30, or 90 days \(Zoho filters on customerResponseTime, despite the name\) |
| `sortBy` | string | No | Sort field: createdTime, customerResponseTime, or responseDueDate. Prefix with - for descending. |
| `include` | string | No | Comma-separated related data to embed. Allowed: contacts, products, departments, team, isRead, assignee |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tickets` | array | List of tickets |
| ↳ `id` | string | Ticket ID |
| ↳ `ticketNumber` | string | Human-readable ticket number |
| ↳ `subject` | string | Ticket subject |
| ↳ `description` | string | Ticket description \(raw; may be HTML\) |
| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim |
| ↳ `status` | string | Ticket status |
| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) |
| ↳ `priority` | string | Ticket priority |
| ↳ `category` | string | Ticket category |
| ↳ `subCategory` | string | Ticket sub-category |
| ↳ `classification` | string | Ticket classification |
| ↳ `channel` | string | Origin channel |
| ↳ `departmentId` | string | Department ID |
| ↳ `contactId` | string | Contact ID |
| ↳ `accountId` | string | Account ID |
| ↳ `assigneeId` | string | Assignee ID |
| ↳ `email` | string | Contact email |
| ↳ `phone` | string | Contact phone |
| ↳ `dueDate` | string | Due date |
| ↳ `responseDueDate` | string | Response due date |
| ↳ `createdTime` | string | Created timestamp |
| ↳ `modifiedTime` | string | Last modified timestamp |
| ↳ `customerResponseTime` | string | Time the last customer response was received |
| ↳ `closedTime` | string | Closed timestamp |
| ↳ `resolution` | string | Resolution text |
| ↳ `threadCount` | string | Number of threads |
| ↳ `commentCount` | string | Number of comments |
| ↳ `webUrl` | string | Web URL to the ticket |
| ↳ `isEscalated` | boolean | Whether the ticket is escalated |
| ↳ `isOverDue` | boolean | Whether the ticket is overdue |
| ↳ `isSpam` | boolean | Whether the ticket is marked spam |
| ↳ `cf` | json | Custom field values, keyed by custom field API name |
| `count` | number | Number of tickets returned |
### Zoho Desk Get Ticket
Retrieve a single Zoho Desk ticket by ID.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID to retrieve |
| `include` | string | No | Comma-separated related data to embed. Allowed: contacts, products, assignee, departments, contract, isRead, team, skills |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ticket` | object | The ticket |
| ↳ `id` | string | Ticket ID |
| ↳ `ticketNumber` | string | Human-readable ticket number |
| ↳ `subject` | string | Ticket subject |
| ↳ `description` | string | Ticket description \(raw; may be HTML\) |
| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim |
| ↳ `status` | string | Ticket status |
| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) |
| ↳ `priority` | string | Ticket priority |
| ↳ `category` | string | Ticket category |
| ↳ `subCategory` | string | Ticket sub-category |
| ↳ `classification` | string | Ticket classification |
| ↳ `channel` | string | Origin channel |
| ↳ `departmentId` | string | Department ID |
| ↳ `contactId` | string | Contact ID |
| ↳ `accountId` | string | Account ID |
| ↳ `assigneeId` | string | Assignee ID |
| ↳ `email` | string | Contact email |
| ↳ `phone` | string | Contact phone |
| ↳ `dueDate` | string | Due date |
| ↳ `responseDueDate` | string | Response due date |
| ↳ `createdTime` | string | Created timestamp |
| ↳ `modifiedTime` | string | Last modified timestamp |
| ↳ `customerResponseTime` | string | Time the last customer response was received |
| ↳ `closedTime` | string | Closed timestamp |
| ↳ `resolution` | string | Resolution text |
| ↳ `threadCount` | string | Number of threads |
| ↳ `commentCount` | string | Number of comments |
| ↳ `webUrl` | string | Web URL to the ticket |
| ↳ `isEscalated` | boolean | Whether the ticket is escalated |
| ↳ `isOverDue` | boolean | Whether the ticket is overdue |
| ↳ `isSpam` | boolean | Whether the ticket is marked spam |
| ↳ `cf` | json | Custom field values, keyed by custom field API name |
### Zoho Desk Update Ticket
Update fields on an existing Zoho Desk ticket.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID to update |
| `subject` | string | No | Ticket subject |
| `status` | string | No | Ticket status \(e.g. Open, Closed\) |
| `priority` | string | No | Ticket priority \(e.g. High\) |
| `assigneeId` | string | No | Assignee \(agent\) ID |
| `departmentId` | string | No | Department ID |
| `category` | string | No | Ticket category |
| `subCategory` | string | No | Ticket sub-category |
| `dueDate` | string | No | Due date \(ISO 8601\) |
| `description` | string | No | Ticket description |
| `resolution` | string | No | Resolution notes recorded on the ticket |
| `classification` | string | No | Ticket classification. Zoho's system-defined values are Problem, Request, and Question; portals can define custom values. Pass "" to clear it. |
| `customFields` | json | No | Custom field values as a JSON object, keyed by custom field API name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ticket` | object | The updated ticket |
| ↳ `id` | string | Ticket ID |
| ↳ `ticketNumber` | string | Human-readable ticket number |
| ↳ `subject` | string | Ticket subject |
| ↳ `description` | string | Ticket description \(raw; may be HTML\) |
| ↳ `descriptionText` | string | Plain-text rendering of the description: HTML stripped when the body contains markup, otherwise the description verbatim |
| ↳ `status` | string | Ticket status |
| ↳ `statusType` | string | Status category \(Open/Closed/On Hold\) |
| ↳ `priority` | string | Ticket priority |
| ↳ `category` | string | Ticket category |
| ↳ `subCategory` | string | Ticket sub-category |
| ↳ `classification` | string | Ticket classification |
| ↳ `channel` | string | Origin channel |
| ↳ `departmentId` | string | Department ID |
| ↳ `contactId` | string | Contact ID |
| ↳ `accountId` | string | Account ID |
| ↳ `assigneeId` | string | Assignee ID |
| ↳ `email` | string | Contact email |
| ↳ `phone` | string | Contact phone |
| ↳ `dueDate` | string | Due date |
| ↳ `responseDueDate` | string | Response due date |
| ↳ `createdTime` | string | Created timestamp |
| ↳ `modifiedTime` | string | Last modified timestamp |
| ↳ `customerResponseTime` | string | Time the last customer response was received |
| ↳ `closedTime` | string | Closed timestamp |
| ↳ `resolution` | string | Resolution text |
| ↳ `threadCount` | string | Number of threads |
| ↳ `commentCount` | string | Number of comments |
| ↳ `webUrl` | string | Web URL to the ticket |
| ↳ `isEscalated` | boolean | Whether the ticket is escalated |
| ↳ `isOverDue` | boolean | Whether the ticket is overdue |
| ↳ `isSpam` | boolean | Whether the ticket is marked spam |
| ↳ `cf` | json | Custom field values, keyed by custom field API name |
### Zoho Desk List Comments
List comments on a Zoho Desk ticket.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID |
| `from` | number | No | Pagination start index \(0-based\) |
| `limit` | number | No | Number of comments to return \(1-100, default 50\) |
| `sortBy` | string | No | Sort by commentedTime. Ascending by default; prefix with - for descending \(-commentedTime\). |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `comments` | array | List of comments |
| ↳ `id` | string | Comment ID |
| ↳ `content` | string | Comment content \(raw; may be HTML\) |
| ↳ `contentType` | string | Content type \(plainText/html\) |
| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) |
| ↳ `isPublic` | boolean | Whether the comment is public |
| ↳ `commenterId` | string | Commenter ID |
| ↳ `commenter` | object | Who wrote the comment |
| ↳ `name` | string | Display name |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Email address |
| ↳ `type` | string | Commenter type \(AGENT/END_USER\) |
| ↳ `roleName` | string | Role name |
| ↳ `photoURL` | string | Avatar URL |
| ↳ `commentedTime` | string | Commented timestamp |
| ↳ `modifiedTime` | string | Modified timestamp |
| ↳ `attachments` | array | Comment attachments |
| ↳ `id` | string | Attachment ID |
| ↳ `name` | string | File name |
| ↳ `size` | string | File size as reported by Zoho |
| ↳ `href` | string | Download href |
| `count` | number | Number of comments returned |
### Zoho Desk Add Comment
Add a comment to a Zoho Desk ticket.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID |
| `content` | string | Yes | Comment content |
| `contentType` | string | No | Content type: plainText or html. Defaults to plainText so agent-written text posts literally; pass 'html' to send markup \(Zoho's own API default is html\). |
| `isPublic` | boolean | No | Whether the comment is public |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `comment` | object | The created comment |
| ↳ `id` | string | Comment ID |
| ↳ `content` | string | Comment content \(raw; may be HTML\) |
| ↳ `contentType` | string | Content type \(plainText/html\) |
| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) |
| ↳ `isPublic` | boolean | Whether the comment is public |
| ↳ `commenterId` | string | Commenter ID |
| ↳ `commenter` | object | Who wrote the comment |
| ↳ `name` | string | Display name |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Email address |
| ↳ `type` | string | Commenter type \(AGENT/END_USER\) |
| ↳ `roleName` | string | Role name |
| ↳ `photoURL` | string | Avatar URL |
| ↳ `commentedTime` | string | Commented timestamp |
| ↳ `modifiedTime` | string | Modified timestamp |
| ↳ `attachments` | array | Comment attachments |
| ↳ `id` | string | Attachment ID |
| ↳ `name` | string | File name |
| ↳ `size` | string | File size as reported by Zoho |
| ↳ `href` | string | Download href |
### Zoho Desk List Threads
List conversation threads on a Zoho Desk ticket, newest first (Zoho sorts by sendDateTime descending by default). Returns a list projection: message bodies (content, summary, to/cc/bcc) come back only from Get Thread.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID |
| `from` | number | No | Pagination start index \(0-based\) |
| `limit` | number | No | Number of threads to return \(1-200, default 100\) |
| `sortBy` | string | No | Sort by sendDateTime. Zoho sorts descending \(newest first\) when unset; pass sendDateTime for oldest first. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threads` | array | List of threads |
| ↳ `id` | string | Thread ID |
| ↳ `channel` | string | Thread channel |
| ↳ `direction` | string | Direction \(in/out\) |
| ↳ `content` | string | Thread content \(raw; may be HTML\) |
| ↳ `contentType` | string | Content type |
| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) |
| ↳ `summary` | string | Thread summary |
| ↳ `responderId` | string | Responder ID |
| ↳ `createdTime` | string | Created timestamp |
| ↳ `hasAttach` | boolean | Whether the thread has attachments |
| ↳ `attachmentCount` | string | Number of attachments |
| ↳ `fromEmailAddress` | string | From email address |
| ↳ `to` | string | To email address |
| ↳ `cc` | string | CC email address |
| ↳ `bcc` | string | BCC email address |
| ↳ `replyTo` | string | Reply-to email address |
| ↳ `isForward` | boolean | Whether the thread is a forward |
| ↳ `isContentTruncated` | boolean | Whether Zoho truncated the thread content; fetch fullContentURL for the rest |
| ↳ `fullContentURL` | string | URL returning the untruncated thread content |
| ↳ `plainText` | string | Zoho's own plain-text rendering of the thread, when it supplies one |
| ↳ `status` | string | Delivery status of the thread \(e.g. SUCCESS, PENDING, FAILED, DRAFT\) |
| ↳ `isDescriptionThread` | boolean | Whether this thread is the ticket's original description |
| ↳ `visibility` | string | Thread visibility \(e.g. public\) |
| ↳ `canReply` | boolean | Whether the thread can be replied to |
| ↳ `author` | object | Who sent the thread |
| ↳ `name` | string | Display name |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Email address |
| ↳ `type` | string | Author type \(AGENT/END_USER\) |
| ↳ `photoURL` | string | Avatar URL |
| ↳ `attachments` | array | Thread attachments |
| ↳ `id` | string | Attachment ID |
| ↳ `name` | string | File name |
| ↳ `size` | string | File size as reported by Zoho |
| ↳ `href` | string | Download href |
| `count` | number | Number of threads returned |
### Zoho Desk Get Thread
Retrieve the full content of a single Zoho Desk ticket thread.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `ticketId` | string | Yes | Ticket ID |
| `threadId` | string | Yes | Thread ID |
| `include` | string | No | Related data to embed. Allowed: plainText — Zoho's own plain-text rendering of the thread |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `thread` | object | The thread |
| ↳ `id` | string | Thread ID |
| ↳ `channel` | string | Thread channel |
| ↳ `direction` | string | Direction \(in/out\) |
| ↳ `content` | string | Thread content \(raw; may be HTML\) |
| ↳ `contentType` | string | Content type |
| ↳ `contentText` | string | Plain-text rendering of content \(HTML stripped when contentType is html\) |
| ↳ `summary` | string | Thread summary |
| ↳ `responderId` | string | Responder ID |
| ↳ `createdTime` | string | Created timestamp |
| ↳ `hasAttach` | boolean | Whether the thread has attachments |
| ↳ `attachmentCount` | string | Number of attachments |
| ↳ `fromEmailAddress` | string | From email address |
| ↳ `to` | string | To email address |
| ↳ `cc` | string | CC email address |
| ↳ `bcc` | string | BCC email address |
| ↳ `replyTo` | string | Reply-to email address |
| ↳ `isForward` | boolean | Whether the thread is a forward |
| ↳ `isContentTruncated` | boolean | Whether Zoho truncated the thread content; fetch fullContentURL for the rest |
| ↳ `fullContentURL` | string | URL returning the untruncated thread content |
| ↳ `plainText` | string | Zoho's own plain-text rendering of the thread, when it supplies one |
| ↳ `status` | string | Delivery status of the thread \(e.g. SUCCESS, PENDING, FAILED, DRAFT\) |
| ↳ `isDescriptionThread` | boolean | Whether this thread is the ticket's original description |
| ↳ `visibility` | string | Thread visibility \(e.g. public\) |
| ↳ `canReply` | boolean | Whether the thread can be replied to |
| ↳ `author` | object | Who sent the thread |
| ↳ `name` | string | Display name |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Email address |
| ↳ `type` | string | Author type \(AGENT/END_USER\) |
| ↳ `photoURL` | string | Avatar URL |
| ↳ `attachments` | array | Thread attachments |
| ↳ `id` | string | Attachment ID |
| ↳ `name` | string | File name |
| ↳ `size` | string | File size as reported by Zoho |
| ↳ `href` | string | Download href |
### Zoho Desk Get Contact
Retrieve a Zoho Desk contact by ID.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `contactId` | string | Yes | Contact ID to retrieve |
| `include` | string | No | Comma-separated related data to embed. Allowed: accounts, owner |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `contact` | object | The contact |
| ↳ `id` | string | Contact ID |
| ↳ `firstName` | string | First name |
| ↳ `lastName` | string | Last name |
| ↳ `email` | string | Primary email |
| ↳ `secondaryEmail` | string | Secondary email |
| ↳ `phone` | string | Phone number |
| ↳ `mobile` | string | Mobile number |
| ↳ `accountId` | string | Associated account ID |
| ↳ `ownerId` | string | Owner ID |
| ↳ `type` | string | Contact type |
| ↳ `title` | string | Job title |
| ↳ `street` | string | Street |
| ↳ `city` | string | City |
| ↳ `state` | string | State |
| ↳ `country` | string | Country |
| ↳ `zip` | string | ZIP / postal code |
| ↳ `description` | string | Description |
| ↳ `cf` | json | Custom field values, keyed by custom field API name |
### Zoho Desk Get Attachment
Download a Zoho Desk ticket attachment (from its href) as a file.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `orgId` | string | Yes | Zoho Desk organization ID |
| `href` | string | Yes | Attachment download href \(from a thread or comment attachment\) |
| `fileName` | string | No | Optional file name for the downloaded file |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `file` | file | The downloaded attachment file |
### Zoho Desk List Organizations
List the Zoho Desk organizations (portals) the connected account can access.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `organizations` | array | Accessible organizations |
| ↳ `id` | string | Organization ID |
| ↳ `companyName` | string | Company name |
| ↳ `portalName` | string | Portal name |
| `count` | number | Number of organizations returned |
## Triggers
A **Trigger** is a block that starts a workflow when an event happens in this service.
### Zoho Desk Event
Trigger a workflow when a Zoho Desk event occurs (ticket, comment, thread, contact, agent, task, or article changes).
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerCredentials` | string | Yes | This trigger creates and manages a webhook subscription in your Zoho Desk account. |
| `orgId` | project-selector | Yes | The Zoho Desk organization \(portal\) to subscribe in. |
| `manualOrgId` | string | Yes | Type an organization ID instead of picking one from the list. |
| `eventType` | string | Yes | Event |
| `triggerDepartmentIds` | string | No | Restrict events to these departments. Leave empty for all departments. |
| `fields` | string | No | For Ticket Updated: only fire when one of these fields changes \(max 5\). Previous values are included in the payload. |
| `direction` | string | No | Thread Direction |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | The Zoho Desk event type \(e.g. Ticket_Add\) |
| `eventTime` | string | Event time in milliseconds since epoch |
| `orgId` | string | Zoho Desk organization ID |
| `payload` | json | The full resource that changed \(ticket, comment, thread, etc.\). Comment and thread events gain a derived plain-text `contentText` alongside the raw `content` + `contentType`; ticket events gain `descriptionText` alongside `description`. |
| `prevState` | json | Previous state of the resource \(update events only\) |