Zoho / ManageEngine Endpoint Central Cloud connector
A native Zoho OAuth 2.0 connector that pulls patch posture from
ManageEngine Endpoint Central Cloud into TATER's Application
Monitoring surface as source=zoho-ec findings. Designed to be
extensible to any Zoho-platform service (other ManageEngine modules, Zoho
Desk, etc.) without writing a new connector each time.
Tracked in ADO #596 (build) and #598 (verified spec / gotchas).
What it does
- Authenticates to Zoho via OAuth 2.0 self-client refresh-token flow. Access tokens are cached per (org, service) and auto-refreshed before the 1-hour expiry.
- Pulls the
/dcapi/threats/patchesfeed from your Endpoint Central Cloud instance, walking pagination viametadata.links.next. - Upserts one Application Monitoring finding per patch (deduped on bulletin / KB / patch id) with severity, vendor, CVE refs, download status, and per-system installed / missing / failed counts.
- Surfaces in TATER's Application Monitoring page, the
list_monitoring_findingsMCP tool withsource=zoho-ec, and the dedicated MCP toolslist_ec_patchesandsync_ec_patches.
One-time setup
- In your Zoho region's developer console
(
api-console.zoho.<region>, e.g..com,.eu,.in), create a Self Client application. Note the Client ID + Client Secret. - From the same Self Client, generate a grant token
(authorization_code, 3-minute expiry) with scope
DesktopCentralCloud.PatchMgmt.READ. Trade the grant code for a refresh token athttps://accounts.zoho.<region>/oauth/v2/token. Refresh tokens are long-lived; store the refresh token only. - Confirm your EC Cloud instance host. This is the host
that serves
/dcapi/*routes — typicallyhttps://endpointcentral.manageengine.com. The TATER connector requires this explicitly. Do NOT use the token response'sapi_domain— it points atwww.zohoapis.comwhich returns HTTP 400 "Invalid URL" for EC routes. This was the #1 gotcha while verifying the spec for #598. - In TATER, open Manage → Integrations → Zoho (or POST to
/api/settingswithintegrations.zoho = {…}). Provide:region,clientId,clientSecret,refreshToken,apiInstanceHost, optionalscope. Setenabled: true. - Test the connection:
POST /api/zoho/test. A successful response shows the total reachable EC patch records. Stamp will appear on the config aslastTestedAt/lastTestResult.
Secret handling
clientSecret and refreshToken are encrypted at
rest using TATER's AES-256-GCM ENCRYPTION_KEY. The settings GET
endpoint never returns either decrypted — they always show
as [REDACTED]. Submitting the literal string [REDACTED]
on save preserves the stored value, so admins can update the region or
instance host without re-entering the secret.
This mirrors the existing ADO PAT pattern in
Settings.integrations.adoTasks.pat — no need to involve a
separate Key Vault. If you want strict KV-backed secrets for compliance,
front the settings endpoint with a Key Vault reference resolver and store
the KV secret name in the config instead of the value.
Syncing patch posture
Three ways to trigger:
- REST:
POST /api/zoho/ec/patches/syncwith body{"failedOnly":true,"maxPages":20,"pageLimit":100}. DefaultfailedOnly=truefilters out fully-installed / already-downloaded patches with zero missing systems — those are not actionable signals. Returns{scanned, upserted, totalReported}. - MCP:
sync_ec_patches(Admin) — same logic, agent-callable. Pair withlist_ec_patchesfor read-back. - Scheduled: wire a TATER Ops cron task that calls the REST endpoint via the platform's existing scheduled-runner. Daily is a reasonable starting cadence; tighter for fleets under active rollout.
What lands in Application Monitoring
Each finding carries:
source:zoho-eckind:EC Cloud patch postureresource:ec-patch:<bulletinid|kb|patchid>— the dedup key, so re-syncing updates in place.resourceLabel: vendor / bulletin / patchname.severity: mapped from EC's severity field (Critical / Important / Moderate / Low).detail: update type, platform, missing/failed system counts, download status, patch status.tags:zoho-ec,vendor:<name>, plus any offailed/download-failed/missing.
Findings inherit the standard MonitoringFindings lifecycle (open / acknowledged / suppressed / remediated / superseded). Acknowledge or promote to a TaskerTask as usual.
Important gotcha (per #598)
The Zoho token response includes api_domain (typically
https://www.zohoapis.com). This is NOT the EC API
host. Calling /dcapi/threats/patches against
www.zohoapis.com returns HTTP 400 with a generic Zoho "Invalid
URL" page. The real API host is your EC Cloud instance host
(e.g. https://endpointcentral.manageengine.com). The TATER
connector ignores api_domain and uses
cfg.apiInstanceHost exclusively. Set it correctly during
setup or every sync will 400.
Extensibility to other Zoho services
The zohoAuth.ts token cache is keyed on
(tenantId, orgId, service) so a single org can hold one shared
refresh token covering multiple Zoho scopes (e.g.
DesktopCentralCloud.PatchMgmt.READ +
ZohoDesk.tickets.READ) without colliding. New connectors should:
- Call
loadZohoConfig()for credentials. - Call
zohoFetch(tenantId, orgId, cfg, path, init, service)— pass a distinctservicestring per scope so token caches don't bleed if a future API requires per-service tokens. - Write findings via
upsertMonitoringFinding()with a distinctsourcevalue (extend theMonitoringSourceunion inapi/src/lib/monitoringFindings.ts).
Data-model note for downloads
The /dcapi/threats/patches route does not
expose a download-source URL per endpoint. Each patch carries:
vendor_name+patchname(installer filename)- A single
download_statusfor the patch (EC server-side) - System counts:
installed_system_count,missing_system_count,failed_system_count
EC handles patch binary download server-side, not per endpoint. If you need to scope GSA / firewall bypass rules based on the downloads EC will attempt, treat the bulletin/KB id as the join key against your firewall vendor's URL category data, not individual binary URLs.
API reference
| Method | Path | Role | Notes |
|---|---|---|---|
| GET | /api/zoho/config | Auditor+ | Returns config with secrets redacted. |
| POST | /api/zoho/test | Admin | Forces fresh token + 1-record probe of EC. Stamps lastTested fields. |
| POST | /api/zoho/ec/patches/sync | Admin | Body: {failedOnly?,maxPages?,pageLimit?}. |
| GET | /api/zoho/ec/patches | Auditor+ | Query: ?state=&limit=. |
MCP tools
list_ec_patches({state?, limit?})— Viewer+ — read current EC findings.sync_ec_patches({failed_only?, max_pages?, page_limit?})— Admin — refresh from EC and upsert findings.