Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/zendesk

README.md

256 lines · 9.9 KB · Markdown

@kody/zendesk

Intent

Provide reusable, account-agnostic Zendesk helpers so Kody agents can read and update tickets, users, and Help Center articles through a saved zendesk / zendesk-* OAuth integration or an API token plus agent email — without hand-rolling REST. The Support subdomain is an input (or packageStorage config.subdomain) so this package is not locked to one account. Mutations are previewable with dryRun: true and only run live after confirm: true.

This listing is meant to be forked. After you fork, connect your Zendesk account. Do not treat the live @kody/zendesk package storage as yours.

Share: https://kody.codes/@kody/zendesk

When To Use

  • List, search, or inspect tickets, ticket comments, users, and articles
  • Create or update tickets, users, and articles after explicit confirmation
  • Call an unwrapped Zendesk REST path through ./request
  • Connect more than one Zendesk account via integrationName / account

Auth

Zendesk has no built-in Kody OAuth app. Choose one lane. Both need your Support subdomain (acme from https://acme.zendesk.com). Pass subdomain on each call, or store it once with ./configure.

LaneWhen to useCredential
OAuth (recommended)Refresh tokens, scoped access, multi-accountSaved integration zendesk or zendesk-<purpose>
API token + emailFastest for a single accountUser secrets zendeskApiUsername ({email}/token) and zendeskApiToken

OAuth sends Authorization: Bearer …. API tokens use HTTP Basic {email}/token:{api_token}. Required API host: {your-subdomain}.zendesk.com. Approve it in the account secrets UI.

Store your subdomain
import configure from 'kody:@kody/zendesk/configure'

export default async function main() {
	return await configure({
		subdomain: 'acme',
		email: 'agent@example.com',
	})
}

That writes packageStorage config for this saved package copy. Substitute your subdomain. Never paste tokens into chat.

Lane A — BYO OAuth
  1. In Admin Center open Apps and integrations → APIs → OAuth clients (https://your-subdomain.zendesk.com/admin/apps-integrations/apis/oauth_clients)
  2. Create a Confidential client
  3. Set the redirect URI exactly to https://kody.codes/connect/oauth
  4. Connect while signed in to Kody (replace your-subdomain):

https://kody.codes/connect/oauth?provider=zendesk&authorizeUrl=https%3A%2F%2Fyour-subdomain.zendesk.com%2Foauth%2Fauthorizations%2Fnew&tokenUrl=https%3A%2F%2Fyour-subdomain.zendesk.com%2Foauth%2Ftokens&apiBaseUrl=https%3A%2F%2Fyour-subdomain.zendesk.com%2Fapi%2Fv2&scopes=read%20write%20tickets%3Aread%20tickets%3Awrite%20users%3Aread%20users%3Awrite%20hc%3Aread%20hc%3Awrite&flow=confidential&pkce=false&allowedHosts=your-subdomain.zendesk.com&dashboardUrl=https%3A%2F%2Fyour-subdomain.zendesk.com%2Fadmin%2Fapps-integrations%2Fapis%2Foauth_clients

  1. Paste the Zendesk client id and client secret into the Kody wizard (never into chat). Approve host {your-subdomain}.zendesk.com.
  2. Reconnect later with https://kody.codes/connect/oauth?provider=zendesk

OAuth authorize URL: https://{subdomain}.zendesk.com/oauth/authorizations/new. Token URL: https://{subdomain}.zendesk.com/oauth/tokens. Flow: confidential (client secret). Scopes are space-separated.

To connect a second account, change provider (for example provider=zendesk-work) and pass integrationName: 'zendesk-work' plus that account's subdomain on every call.

Lane B — API token + email
  1. Create a token at Apps and integrations → APIs → Zendesk API (https://your-subdomain.zendesk.com/admin/apps-integrations/apis/zendesk-api/settings)
  2. Save the Basic username {agent-email}/token (do not paste the value in chat):

https://kody.codes/account/secrets/new?name=zendeskApiUsername&description=Zendesk%20API%20username%20(your-agent-email%2Ftoken)&allowedHosts=your-subdomain.zendesk.com&scope=user

  1. Save the API token:

https://kody.codes/account/secrets/new?name=zendeskApiToken&description=Zendesk%20API%20token%20from%20Admin%20Center%20%E2%86%92%20Zendesk%20API&allowedHosts=your-subdomain.zendesk.com&scope=user

For a second account, use distinct secret names such as zendeskApiToken-work / zendeskApiUsername-work and pass secretName / usernameSecretName (or account: 'work').

Scopes

ScopeNeeded for
tickets:read./list-tickets, ./get-ticket, ./search-tickets, ./list-ticket-comments
tickets:write./create-ticket, ./update-ticket, ./add-ticket-comment
users:read./list-users, ./get-user, ./search-users, ./me, ./smoke-test
users:write./create-user, ./update-user
hc:read./list-articles, ./get-article, ./search-articles
hc:write./create-article, ./update-article
read / writeBroad access when granular scopes are unavailable

If Zendesk returns 401/403 or an insufficient-scope error, helpers throw a message that names the missing scope and the next setup URL (reconnect OAuth with that scope, or save an API token).

Multiple accounts

Every export accepts:

  • subdomain — required unless stored via ./configure
  • email — optional API-token hint; also stored by ./configure
  • integrationName / integration — exact saved OAuth name (zendesk-work)
  • accountworkzendesk-work; zendesk-work used as-is; omitted → zendesk
  • secretName / usernameSecretName — API-token secret overrides
  • auth'oauth' or 'apiToken' when both exist

Do not hard-code a personal subdomain as the only option.

Safety

Mutating helpers require confirm: true. Pass dryRun: true to inspect the REST payload without calling Zendesk. ./request treats GET and search paths as read-only; POST / PUT / PATCH / DELETE need confirmation.

The account-health job is disabled by default.

Exports

ExportDescription
.Package overview, connect URLs, export map
./accountsResolve integration/secret names and report what is connected
./configureStore subdomain / email in this package's packageStorage
./smoke-testLocal helper checks plus optional live /users/me (no email)
./meAuthenticated agent id / name / role
./list-ticketsList tickets
./get-ticketGet one ticket
./search-ticketsSearch tickets (type:ticket is added when missing)
./create-ticketPreview or create a ticket
./update-ticketPreview or update a ticket
./list-ticket-commentsList comments on a ticket
./add-ticket-commentPreview or add a ticket comment
./list-usersList users
./get-userGet one user
./search-usersSearch users
./create-userPreview or create a user
./update-userPreview or update a user
./list-articlesList Help Center articles
./get-articleGet one article
./search-articlesSearch Help Center articles
./create-articlePreview or create an article (sectionId)
./update-articlePreview or update an article
./requestGeneric REST escape hatch
./scheduled-account-healthDisabled job wrapper: /users/me plus first-page counts
./typesShared TypeScript types

Smoke test

import smokeTest from 'kody:@kody/zendesk/smoke-test'

export default async function main() {
	return await smokeTest()
}

Without credentials this returns { ok: true, live: false } plus the connect and secret URLs. After OAuth or an API token is saved and a subdomain is available it reads /users/me and returns { live: true, hasUserId } without email.

Preview a mutation without credentials:

import createTicket from 'kody:@kody/zendesk/create-ticket'

export default async function main() {
	return await createTicket({
		subject: 'Follow up on onboarding',
		comment: 'Customer asked for a status update.',
		subdomain: 'acme',
		dryRun: true,
	})
}

Examples

import listTickets from 'kody:@kody/zendesk/list-tickets'
import searchUsers from 'kody:@kody/zendesk/search-users'

export default async function main() {
	const tickets = await listTickets({ subdomain: 'acme', perPage: 10 })
	const users = await searchUsers({ subdomain: 'acme', query: 'pat@example.com' })
	return { ticketCount: tickets.items.length, userCount: users.items.length }
}
import createTicket from 'kody:@kody/zendesk/create-ticket'

export default async function main() {
	const preview = await createTicket({
		subject: 'Investigate checkout timeout',
		comment: 'Seen on mobile Safari.',
		subdomain: 'acme',
		dryRun: true,
	})
	// After the user confirms the exact ticket:
	return await createTicket({
		subject: 'Investigate checkout timeout',
		comment: 'Seen on mobile Safari.',
		subdomain: 'acme',
		confirm: true,
	})
}

Unwrapped REST:

import request from 'kody:@kody/zendesk/request'

export default async function main() {
	return await request({
		path: '/users/me',
		subdomain: 'acme',
	})
}

Notes

  • REST base: https://{subdomain}.zendesk.com/api/v2
  • Helpers project slim objects. Use ./request when you need extra fields.
  • ./search-tickets prefixes type:ticket when the query does not already include it.
  • Creating an article needs a Help Center sectionId.
  • This package is not affiliated with or endorsed by Zendesk, Inc.

Branding

The community icon is Zendesk's official Z symbol (the four-shape mark published in Zendesk brand resources / Simple Icons). Paths are unmodified. Fill is Zendesk Green Kelp #03363D. Zendesk® is a trademark of Zendesk, Inc.

Docs