Create, update, and search Zendesk support tickets, post replies and internal notes, and look up or create Zendesk end users, directly from Kizen agentic workflows.
This plugin connects a Zendesk account to Kizen and exposes seven actions as Agentic Workflow Code Steps.
| Action | Description |
|---|---|
| Create Ticket | Opens a new ticket with a title and description; optionally sets requester, priority, type, status, tags, assignee, group, external ID, and custom fields. |
| Update Ticket | Changes status, priority, type, assignee, group, tags, or custom fields on an existing ticket. Only the fields set are changed. |
| Add Ticket Comment | Posts a public reply or an internal note on an existing ticket, optionally changing status at the same time. |
| Get Ticket | Fetches a ticket's current state, including the requester's email. |
| Get Ticket Comments | Fetches a ticket's full conversation thread, pre-flattened into one formatted transcript, plus the most recent comment on its own. |
| Search Tickets | Finds tickets by requester, status, priority, type, tags, organization, external ID, or date; or a raw Zendesk search query for full control. |
| Find or Create User | Looks up a Zendesk end user by Zendesk User ID, External ID, email, or name (in that priority order), optionally creating one if no match exists. |
This plugin uses a business-level OAuth 2.0 connection — one shared Zendesk account connects on behalf of the whole business, rather than each user connecting individually. Every action runs as a Kizen Code Step, which always executes as a fixed service account and cannot complete a per-user OAuth consent flow.
- Create a Zendesk OAuth client. In your Zendesk account: Admin Center → Apps and integrations → APIs → Zendesk API → OAuth Clients → Add OAuth client.
- Grant type: Authorization Code (not Client Credentials — that grant type doesn't issue refresh tokens)
- Client kind: Confidential
- Register Kizen's OAuth redirect URI
- Note the Client ID and Client Secret
- Grant the required scopes on the OAuth client (see Scopes below).
- Connect in Kizen. Navigate to App Marketplace → Zendesk, install the plugin, and complete the OAuth "Connect" flow, authorizing the connection for your business.
| Scope | Purpose |
|---|---|
read |
Required specifically by Search Tickets (/api/v2/search.json) — the granular scopes below aren't accepted as a substitute there. |
tickets:read |
Get Ticket, Get Ticket Comments, and other ticket-read paths. |
tickets:write |
Create Ticket, Update Ticket, Add Ticket Comment. |
users:read |
Find or Create User (lookup), Get Ticket's requester-email resolution. |
users:write |
Find or Create User (creation branch). |
Zendesk's scopes mix coarse (read/write) and resource-scoped (tickets:read, etc.) forms that aren't interchangeable per-endpoint — if an endpoint 403s despite having what looks like the right scope, it may want the coarse form instead. Adding a new scope requires reconnecting the OAuth connection afterward; an already-connected token doesn't retroactively gain it.
Zendesk's refresh tokens are single-use and rotate on every refresh. If a step fails with a token_expired error, reconnect the plugin and retry.
Inputs: subject (Ticket Title)*, comment_body (Description)*, is_public* (boolean), requester_email, requester_name, priority, type (Ticket Type), ticket_status, ticket_tags (comma-separated), assignee_id, group_id, external_id, custom_fields (JSON array string).
Outputs: ticket_id, ticket_url, ticket_status, created_at.
requester_emailfor a brand-new requester requiresrequester_name— Zendesk rejects user creation without one.ticket_status = "pending"requiresassignee_id.ticket_status = "new"isn't guaranteed to stick — Zendesk may report backopeninstead.ticket_status = "hold"may fail on accounts with Zendesk's Custom Ticket Statuses feature enabled.- Omitting
ticket_statusdefaults toopen, notnew.
Inputs: ticket_id*, everything else optional (ticket_status, priority, type, assignee_id, group_id, add_tags, remove_tags, custom_fields). Raises no_fields_to_update if nothing besides ticket_id is set.
Outputs: ticket_id, ticket_status, updated_at.
add_tags and remove_tags are independent comma-separated lists — set either or both in the same call to add some tags and remove others at once. Both are applied via Zendesk's dedicated tags endpoints (PUT/DELETE /api/v2/tickets/{id}/tags.json), not the main ticket-update body — Zendesk doesn't accept tag changes there. Tags not listed in either field are always left untouched. Zendesk does not allow adding or removing tags on a closed ticket.
When updating ticket fields (not just tags), this action fetches the ticket's current state first and submits the update with Zendesk's safe_update protection — if someone else changed the ticket in the meantime, Zendesk rejects the write with a conflict error instead of silently overwriting their change.
Inputs: ticket_id*, body*, is_public* (boolean), ticket_status (optional).
Outputs: comment_id, created_at, is_public.
The comment's comment_id/created_at/is_public are read from the update response's audit.events entry for the comment, not a separate follow-up call — this avoids a race condition where a concurrent update from another user or automation could otherwise cause a "fetch the newest comment" call to return the wrong comment.
Inputs: ticket_id* only.
Outputs: subject (Ticket Title), description, ticket_status, priority, type (Ticket Type), ticket_tags, requester_email, requester_id, assignee_id, group_id, organization_id, external_id, created_at, updated_at, ticket_url.
The ticket object only carries requester_id, not email — a second call, GET /users/{requester_id}.json, resolves it. If that lookup fails, requester_email is left blank rather than failing the whole action.
Inputs: ticket_id*, public_only (boolean), limit (string, default 100).
Outputs: thread_text (formatted transcript, oldest to newest), comment_count, last_comment_body, last_comment_is_public, last_comment_author_id.
Paginates for real, up to a 1000-comment safety cap, fetching newest-first so limit means "N most recent," then reversing that slice into chronological order. Uses Zendesk's include=users side-loading to resolve commenter names in one pass. If the resulting transcript would exceed Kizen's 50,000-character output limit, the oldest comments are dropped first (and comment_count reflects what's actually included) until it fits.
Inputs: all optional — requester_email (email), ticket_status, priority, type (Ticket Type), ticket_tags, organization_id, created_after/updated_after (datetime), external_id, raw_query, limit (string, default 100).
Outputs: tickets (JSON array string, each with id/subject/status/priority/requester_id/updated_at/ticket_url), count.
Always prefixed type:ticket. ticket_tags matches tickets having any of the listed tags — Zendesk treats repeated tags: clauses as OR, not AND, unlike combining different filter types (which does AND together). raw_query, when set, replaces every structured filter above rather than merging both. created_after/updated_after accept a date or datetime; Zendesk honors the time-of-day and timezone if given, rather than only comparing dates. Paginates up to Zendesk's hard 1,000-result cap (100/page × 10 pages); if the resulting JSON would exceed Kizen's 50,000-character output limit, the oldest tickets are dropped first (and count reflects what's actually included) until it fits.
Inputs: zendesk_user_id, external_id, user_email, user_name, phone, organization_id, create_if_missing (boolean, default false).
Outputs: user_id, user_name, user_email, organization_id, role, was_created.
At least one of zendesk_user_id, external_id, user_email, or user_name must be set. Search precedence: Zendesk User ID → External ID → Email → Name — each provided key is tried in that order until one matches, rather than only using the single highest-priority key that's set. Zendesk User ID is a direct record fetch by primary key (a nonexistent ID is treated as "no match," not an error, and falls through to the next key). External ID uses Zendesk's dedicated, guaranteed-unique lookup. Email is an exact match. Name matching is fuzzy, not exact — a partial name can match, so prefer Zendesk User ID or External ID whenever available. If a search (by any key) matches more than one user, the action raises ambiguous_match rather than silently picking one. If none of the provided keys finds a match and create_if_missing is true, a user is created (requires user_name); if false (the default), every output comes back blank/false rather than raising.
- Rate limits are plan-dependent (roughly 200–2,500 requests/min account-wide); Update Ticket is additionally capped at 100/min account-wide and 30 per 10 minutes per user per ticket.
/api/v2/searchcaps at 1,000 results. Search Tickets and Get Ticket Comments respect these via pagination.