agentleFS
ShanksgShanksg/clawskills/skills/zendesk/skill.md

zendesk

Skill2 starsChanged 3 months agoMITView on GitHub

Last validated: 2026-10-14 | API: Zendesk Support REST API v2 Base URL: https://{subdomain}.zendesk.com/api/v2/ ⚠️ Breaking changes since 2024: - OAuth implicit grant and password grant flows deprecated as of February 17, 2025 — use Authorization Code grant (with PKCE for public clients), or client_credentials for confidential server-side clients. - www.zopim.com/api/v2 (legacy Zopim Chat REST API) retired February 28, 2025 — use {subdomain}.zendesk.com/api/v2/chat instead. - Old-style HTTP Target webhooks are superseded by the Webhooks API (/api/v2/webhooks) — migrate any remaining HTTP…

  • Sends data out

3 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions3 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 3

    1. alanalvestech/hitank★ 38same name
    2. andmarios/zendesk-skill★ 8same name
    3. apideck-libraries/api-skills★ 4same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository13 other files it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

47 rules. 0 are common, 23 are distinctive, and 24 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 23

    Distinctive

    This author's own choices. Worth a look.

  • 24

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Only solved tickets can transition to closed (done by automation, not API directly).

    line 92
  2. Very specific:

    Use GET /api/v2/ticket_fields to enumerate all field definitions and map id to key/title.

    line 106
  3. Distinctive:

    All resource IDs are integers.

    line 120
  4. Very specific:

    Principle of least privilege: Use tickets:read tickets:write users:read for a ticket-management integration rather than the blanket read …

    line 185
  5. Distinctive:

    Never share OAuth tokens across tenants.

    line 198
  6. Distinctive:

    Goal: Create a new support ticket when an external system (e.g., billing failure, monitoring alert) fires an event.

    line 207
  7. Very specific:

    If user not found, create them (or let Zendesk create on ticket POST by providing requester object).

    line 220
  8. Very specific:

    If you provide requester.email and the user already exists, Zendesk links automatically.

    line 246
  9. Very specific:

    If subject is missing, Zendesk uses the first line of the comment body — always provide it explicitly.

    line 247
  10. Distinctive:

    Avoid sending PII (full card numbers, passwords) in the ticket body.

    line 248
  11. Distinctive:

    Use the Search API:

    line 262
  12. Very specific:

    Search queries are URL-encoded.

    line 290
  13. Distinctive:

    PUT to the ticket endpoint:

    line 304
  14. Very specific:

    If you include a comment in a PUT, it is added atomically with the update (single audit event).

    line 329
  15. Distinctive:

    Goal: Add an internal note (not visible to the requester) for agent handoff context.

    line 337
  16. Very specific:

    PUT the ticket with a comment where public: false:

    line 343
  17. Distinctive:

    Use the upload token in a ticket comment:

    line 404
  18. Very specific:

    Create a Trigger in Admin Center (or via API) that fires on ticket.priority = urgent and uses the webhook as the action target.

    line 461
  19. Distinctive:

    PUT to bulk update endpoint (max 100 IDs per request):

    line 487
  20. Very specific:

    If any ticket in the batch fails, the job may still complete partially — check results array in the completed job status for per-ticket …

    line 519
  21. Very specific:

    Use update_many for homogeneous changes like tags, assignee, or status across a known set of ticket IDs.

    line 525
  22. Very specific:

    Keep request batches at 100 IDs max and persist the returned job_status_id.

    line 526
  23. Distinctive:

    If the job partially succeeds, re-run only the failed IDs after inspecting the job result.

    line 527
  24. Distinctive:

    Goal: Add a new agent to a support group and verify their access.

    line 533
  25. Distinctive:

    Create a group membership:

    line 539
  26. Distinctive:

    Only admins can manage group memberships via API.

    line 570
  27. Distinctive:

    Search Jira for an existing issue using that key in a custom field or label.

    line 590
  28. Distinctive:

    If no issue exists, create a Jira Bug with the Zendesk URL, customer impact, and reproduction details.

    line 591
  29. Distinctive:

    Write the Jira issue key back to Zendesk as an internal note or custom field so agents can track engineering progress.

    line 592
  30. Distinctive:

    Keep status sync one-way at first.

    line 606
  31. Very specific:

    Start with start_time at least 5 minutes in the past.

    line 672
  32. Very specific:

    Search API: &sort_by=created_at&sort_order=asc (or desc).

    line 680
  33. Very specific:

    Before creating a ticket from an external event, search by external_id first:

    line 686
  34. Very specific:

    When exceeded, Zendesk returns HTTP 429 Too Many Requests with a Retry-After header (seconds).

    line 705
  35. Very specific:

    Keep writes and reads separate: use update_many for operational changes, Incremental Export for system-of-record ingestion.

    line 729
  36. Very specific:

    Ticket creation: Use external_id on the ticket.

    line 745
  37. Distinctive:

    Run multiple parallel API calls carefully.

    line 752
  38. Very specific:

    Webhook not firing: Verify the Trigger condition matches the ticket data exactly.

    line 778
  39. Distinctive:

    Use Zendesk's own audit log (Admin Center > Account > Audit log) to verify API-driven changes from the Zendesk side.

    line 784
  40. Distinctive:

    Do not log raw ticket bodies in application logs.

    line 794
  41. Very specific:

    Use Zendesk's Redaction API (PUT /api/v2/tickets/{id}/comments/{comment_id}/redact) to permanently remove PII from comments when legally …

    line 795
  42. Very specific:

    When reading tickets for reporting, use ?include= only for the related objects you actually need.

    line 800
  43. Distinctive:

    Do not store Zendesk API tokens or OAuth secrets in data warehouses or logs.

    line 802
  44. Very specific:

    Every ticket update creates an Audit record visible in the ticket's audit history: GET /api/v2/tickets/{id}/audits.json.

    line 807
  45. Very specific:

    Use created_by_id on audit records to distinguish user-driven vs.

    line 808
  46. Distinctive:

    End-user tokens: Not applicable for API integrations.

    line 814
  47. Distinctive:

    Create a dedicated Zendesk agent account (service account) for each integration.

    line 815

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
skill.md

Zendesk Skill

Last validated: 2026-10-14 | API: Zendesk Support REST API v2 Base URL: https://{subdomain}.zendesk.com/api/v2/

⚠️ Breaking changes since 2024:

  • OAuth implicit grant and password grant flows deprecated as of February 17, 2025 — use Authorization Code grant (with PKCE for public clients), or client_credentials for confidential server-side clients.
  • www.zopim.com/api/v2 (legacy Zopim Chat REST API) retired February 28, 2025 — use {subdomain}.zendesk.com/api/v2/chat instead.
  • Old-style HTTP Target webhooks are superseded by the Webhooks API (/api/v2/webhooks) — migrate any remaining HTTP targets.

⚠️ Recently enforced deprecations + new in 2026 (dates per bullet):

  • OAuth token TTL enforcement (live 2026-02-02, announced 2026-01-16): global (external) OAuth clients now have default token expiration and must use the refresh-token flow. Long-lived tokens without a refresh path will start failing.
  • Apps password setting removed (live 2026-04-29): Zendesk apps no longer accept a password value in installation settings.
  • /api/v2/apps/locations/{app_location_id} endpoint removed (live 2026-04-30): migrate to the replacement listed in the Zendesk app framework docs.
  • Create Request endpoint POST /api/v2/requests (changed 2026-03-10): anonymous requests now go through an email verification workflow to reduce spam — adjust integrations that programmatically file requests on behalf of unauthenticated users.

Checked 2026-08-02 — no new Support API v2 breaking changes. Changes since the last review affect adjacent products, not the Support endpoints used in this doc:

  • IT Asset Management (breaking 2026-08-06, announced 2026-07-06): the purchase_cost field on assets becomes an object with amount and currency instead of a scalar. Only affects the ITAM Assets API.
  • AI Agents API (deprecation 2026-07-01, announced 2026-05-19): the legacy *.ultimate.ai base URL is retired — use {subdomain}.zendesk.com/ai-agents/api.
  • Jira integration (2026-06-24): Zendesk is delisting Jira Integration V2 and restoring the original version. Relevant to the Zendesk → Jira bug escalation playbook, which uses the two products' REST APIs directly and is therefore unaffected by the marketplace app change.

Checked 2026-10-14:

  • ⚠️ Changed 2026-08-26: POST /api/v2/oauth/tokens (Create Token) deprecated. The reference now marks it as no longer available. Use Create Token for Grant Type (POST /oauth/tokens, the token endpoint listed under OAuth 2.0 below) instead. This doc's OAuth flow already uses that endpoint.
  • Help Center Templating API v1–v3 end-of-life (starts 2026-09-14): this affects Help Center themes only, not the Support endpoints in this doc.

Source: https://developer.zendesk.com/api-reference/changelog/changelog/

What this skill enables

  • Automate ticket lifecycle management (creation, routing, status updates, closure) from any external event source without manual agent intervention.
  • Deliver consistent customer experiences by programmatically applying macros, triggers, and SLA policies across all channels.
  • Sync customer and organization data bidirectionally between Zendesk and CRM/ERP systems, eliminating duplicate data entry and keeping support context accurate.
  • Power real-time alerting and escalation workflows by consuming Zendesk webhooks to notify downstream systems the moment ticket state changes.
  • Perform high-volume historical imports and bulk operations (mass re-assignment, bulk status change, bulk tagging) at scale without hitting trigger loops.
  • Build self-serve analytics and reporting pipelines using the Incremental Export API to stream all ticket changes since a known point in time.
  • Enforce compliance and audit requirements by capturing every public reply, internal note, and field change through structured activity records.

Best-fit use cases

Use caseWhy it mattersPrimary objects/entitiesTypical triggerSuccess criteria
Create ticket from external alertConverts monitoring, billing, or form events into trackable support work without human triageTicket, User (requester), OrganizationWebhook from PagerDuty / Stripe / web formTicket created with correct priority, requester, and custom fields
Auto-route ticket by skill or orgEnsures the right team sees the ticket immediately, reducing first-response timeTicket, Group, User (assignee), TriggerTicket created or updatedTicket assigned to correct group within seconds
Sync org & user data from CRMKeeps account context (plan tier, ARR, CSM) accurate in Zendesk for agentsOrganization, UserNightly or event-driven CRM webhookZendesk org fields match CRM within defined SLA
Add internal note / agent handoff contextPreserves institutional knowledge so the next agent does not have to re-investigateTicket, Comment (internal)Agent escalation or automationInternal comment visible to agents only; not emailed to requester
Bulk close resolved ticketsKeeps the queue clean; removes stale tickets from SLA reportingTicket (bulk update)Scheduled automation or manager requestAll target tickets moved to solved/closed; audit log entry exists
Webhook-driven escalation to Slack/PagerDutyReduces MTTR by pushing critical ticket events to on-call channelsWebhook, Trigger, TicketTicket priority set to UrgentExternal system receives event payload within 30 seconds
Historical ticket import (migration)Moves legacy data into Zendesk without polluting live queues or firing triggersTicket (import API)One-time or periodic migration jobTickets imported with original timestamps; triggers not fired
Custom field reporting pipelineEnables business-specific metrics (product area, revenue impact) via structured dataTicket Field, Ticket, custom_fields arrayNightly incremental exportAll custom field values present in data warehouse within 24 hours
Merge duplicate ticketsEliminates duplicate work and consolidates requester communicationTicket (merge)Agent identifies duplicates or automation ruleSource ticket linked; all future replies go to target ticket
Attachment collection for complianceArchives all files submitted by customers for legal/compliance reviewAttachment, Upload token, TicketTicket solved or tagged for complianceAttachments downloaded and stored in audit archive
Multi-brand ticket handlingRoutes tickets to the correct brand inbox and applies brand-specific formsBrand, Ticket Form, TicketTicket submitted via brand-specific email/widgetTicket form and brand ID match the submission channel
Proactive CSAT follow-upTriggers satisfaction surveys and logs scores back to Zendesk for reportingTicket, Automation, custom_fieldsTicket solved for N hoursCSAT score written to custom field; survey sent once only

Key concepts & data model

Core objects

ObjectDescriptionKey identifier
TicketThe central work item representing a customer requestid (integer)
UserAny person in Zendesk: requester, submitter, assignee (agent), or end userid (integer); role: end-user, agent, admin
OrganizationGroups of users, typically a company accountid (integer)
GroupCollection of agents who share a queueid (integer)
CommentA reply or note on a ticket. public: true = customer-visible; public: false = internal notepart of Ticket comments array
AttachmentFile linked to a comment via an upload tokentoken (string), then id after attach
Ticket FieldBuilt-in or custom field definitionid (integer); type: text, dropdown, checkbox, date, integer, decimal, regexp, tagger, lookup
Ticket FormOrdered set of ticket fields presented to end usersid (integer)
BrandA distinct support presence (subdomain, email, widget)id (integer)
MacroReusable set of actions applied to a ticket manually by agentsid (integer)
TriggerCondition-action rule that fires automatically on ticket create/updateid (integer)
AutomationTime-based condition-action ruleid (integer)
ViewSaved ticket filter for agent queuesid (integer)
TagFreeform string label on tickets or usersstring value

Ticket status flow

new → open → pending → on-hold → solved → closed
  • new: ticket just created, not yet assigned or touched by an agent.
  • open: agent is actively working it.
  • pending: waiting on the requester.
  • on-hold: waiting on a third party (not the requester).
  • solved: agent marked resolved; CSAT survey can fire.
  • closed: system-sealed after a configured period; cannot be re-opened. Replies to closed tickets create a new follow-up ticket.
  • Only solved tickets can transition to closed (done by automation, not API directly).
  • End users can re-open a solved ticket by replying within the configured window.

Ticket custom fields

Custom field values live in the ticket's custom_fields array:

"custom_fields": [
  { "id": 360001234567, "value": "billing" },
  { "id": 360001234568, "value": true }
]

Use GET /api/v2/ticket_fields to enumerate all field definitions and map id to key/title.

Side-loading

Append ?include=users,organizations,groups to most ticket endpoints to embed related objects in a single response, avoiding N+1 requests:

GET /api/v2/tickets/{id}?include=users,organizations

Response gains users and organizations arrays at the top level.

Naming conventions

  • All resource IDs are integers. Always store as 64-bit integers.
  • Timestamps are ISO 8601 UTC strings (created_at, updated_at).
  • Boolean fields use true/false (JSON), never 1/0.
  • Tag arrays are plain string arrays: ["vip", "billing", "q1-promo"].

Authentication & permissions

Supported auth methods

MethodFormatWhen to use
API Token + Email (Basic)Authorization: Basic base64(email/token:API_TOKEN)Server-to-server integrations, scripts, automation
OAuth 2.0 — Authorization CodeBearer token via Authorization Code flow (+ refresh_token grant)Multi-tenant apps, user-delegated access
OAuth 2.0 — Client CredentialsBearer token from client_id + client_secretServer-to-server integrations on a confidential OAuth client (no user consent step)
Password auth / Implicit grantBasic with real password / ImplicitDeprecated — both removed Feb 17, 2025. Do not use.

API Token (most common for integrations)

  1. In Zendesk Admin Center: Apps and integrations > APIs > Zendesk API > API token.
  2. Generate a token (shown once; store securely).
  3. Encode {email}/token:{api_token} in Base64.
curl -u "agent@example.com/token:YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  "https://{subdomain}.zendesk.com/api/v2/tickets.json"

OAuth 2.0

  • Register an OAuth client under Admin Center > Apps > OAuth Clients.
  • Redirect URI must be HTTPS.
  • Authorization endpoint: https://{subdomain}.zendesk.com/oauth/authorizations/new
  • Token endpoint: https://{subdomain}.zendesk.com/oauth/tokens
  • Scopes are space-separated strings (see below).
  • Supported grant_type values: authorization_code, refresh_token, client_credentials. The password grant is deprecated; implicit grant was switched off Feb 17, 2025 (migrate to Authorization Code + PKCE).

Client credentials (server-to-server). Confidential clients only — public clients can't use it. No user authorization and no refresh token: request a new token when it expires. The token acts as the admin who created the OAuth client, so it stops working if that admin loses access.

curl https://{subdomain}.zendesk.com/oauth/tokens \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "scope": "tickets:read tickets:write"}'

Required OAuth scopes (least privilege)

ScopeGrants
readRead all resources (tickets, users, orgs, etc.)
writeCreate and update tickets, comments, users, orgs
tickets:readRead tickets only
tickets:writeCreate/update tickets only
users:readRead users
users:writeCreate/update users
organizations:readRead organizations
organizations:writeCreate/update organizations
hc:readRead Help Center articles
hc:writeWrite Help Center articles
webhooks:readRead webhook definitions
webhooks:writeCreate/update/delete webhooks

Principle of least privilege: Use tickets:read tickets:write users:read for a ticket-management integration rather than the blanket read write scopes.

Token storage & rotation

  • Store API tokens and OAuth secrets in a secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault). Never in source code or environment variables in CI logs.
  • OAuth access tokens do not expire by default in Zendesk unless the OAuth client is configured for refresh tokens. Configure token expiry and implement refresh flows for long-lived apps.
  • Rotate API tokens on a schedule (quarterly minimum) or immediately after any exposure event.
  • Audit token usage via Admin Center > Apps and integrations > APIs > API token > Activity.

Multi-tenant considerations

  • Each Zendesk account is identified by its subdomain ({subdomain}.zendesk.com). All API calls are scoped to one subdomain.
  • For integrations serving multiple Zendesk accounts, implement per-tenant credential storage keyed by subdomain.
  • Never share OAuth tokens across tenants.
  • Respect per-account rate limits independently — one tenant hitting its limit does not affect another.

Common workflows (recipes)

Recipe 1: Create a ticket from an external event

Goal: Create a new support ticket when an external system (e.g., billing failure, monitoring alert) fires an event.

Preconditions: Valid API token or OAuth token with tickets:write and users:read scopes. Requester email known.

Steps:

  1. Optionally look up or create the requester user by email:
curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/users/search.json?query=email:customer@example.com"
  1. If user not found, create them (or let Zendesk create on ticket POST by providing requester object).
  2. POST the ticket:
curl -X POST \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": {
      "subject": "Payment failed for account #12345",
      "comment": { "body": "Automatic alert: payment of $499 failed at 2026-02-19T08:00:00Z." },
      "requester": { "email": "customer@example.com", "name": "Jane Smith" },
      "priority": "high",
      "tags": ["billing", "auto-created"],
      "custom_fields": [
        { "id": 360001234567, "value": "billing" }
      ]
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/tickets.json"
  1. Parse the response for ticket.id and store it for future updates.

Edge cases / pitfalls:

  • If you provide requester.email and the user already exists, Zendesk links automatically. No need to pre-create.
  • If subject is missing, Zendesk uses the first line of the comment body — always provide it explicitly.
  • Avoid sending PII (full card numbers, passwords) in the ticket body.

Validation: HTTP 201 Created. Confirm ticket.id is present and ticket.status is new.


Recipe 2: Search and retrieve tickets

Goal: Find open billing tickets assigned to a specific group, for reporting or routing.

Preconditions: Token with tickets:read. Know the group ID.

Steps:

  1. Use the Search API:
curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/search.json?query=type:ticket+status:open+group_id:12345+tags:billing&sort_by=created_at&sort_order=desc"
  1. Iterate through results array. Paginate using next_page URL from response.
  2. For incremental sync (polling for changes since last run), use the Incremental Export API instead:
curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/incremental/tickets.json?start_time=1708300800"

start_time is a Unix timestamp (seconds). Response includes end_time — store this as your next start_time.

  1. To get a single ticket with related data:
curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/tickets/98765.json?include=users,organizations"

Edge cases / pitfalls:

  • The Search API returns a maximum of 1000 results. For full exports, use the Incremental Export API.
  • The Incremental Export API enforces a minimum start_time of 5 minutes in the past (to allow indexing).
  • Search queries are URL-encoded; spaces become +.

Validation: Confirm count field in search response matches expected number. For incremental, confirm end_time advances on each call.


Recipe 3: Update ticket status, priority, and assignee

Goal: Move a ticket to pending after sending a customer reply, and reassign it.

Preconditions: Token with tickets:write. Know ticket_id and assignee_id.

Steps:

  1. PUT to the ticket endpoint:
curl -X PUT \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": {
      "status": "pending",
      "assignee_id": 445566,
      "priority": "normal",
      "comment": {
        "body": "Hi Jane, could you confirm the billing email on file?",
        "public": true
      }
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/tickets/98765.json"
  1. Confirm HTTP 200 OK and check ticket.status in response.

Edge cases / pitfalls:

  • Setting status: closed directly via PUT is not supported; tickets go solved → closed via automation.
  • You cannot re-open a closed ticket — replies create a new follow-up ticket automatically.
  • If you include a comment in a PUT, it is added atomically with the update (single audit event).

Validation: Response ticket.status equals "pending". ticket.assignee_id equals 445566.


Recipe 4: Add a public reply vs. an internal note

Goal: Add an internal note (not visible to the requester) for agent handoff context.

Preconditions: Token with tickets:write.

Steps:

  1. PUT the ticket with a comment where public: false:
curl -X PUT \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": {
      "comment": {
        "body": "INTERNAL: Customer confirmed they are on the Enterprise plan. Escalate to Tier 2 if not resolved in 2h.",
        "public": false
      }
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/tickets/98765.json"
  1. For a public reply, set "public": true (or omit public — it defaults to true).

Edge cases / pitfalls:

  • Accidentally setting public: true on an internal note sends an email to the customer. Always be explicit.
  • HTML formatting in body is supported via html_body field. Plain body strips HTML.
  • Mentions (@agent_name) in internal notes do not send notifications via the API — Trigger configuration handles notifications.

Validation: GET the ticket comments and confirm the new comment has public: false in the response.

curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/tickets/98765/comments.json"

Recipe 5: Upload and attach a file to a ticket

Goal: Attach a PDF invoice to a ticket reply.

Preconditions: Token with tickets:write. File available locally or as bytes.

Steps:

  1. Upload the file to get an upload token:
curl -X POST \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice_12345.pdf \
  "https://{subdomain}.zendesk.com/api/v2/uploads.json?filename=invoice_12345.pdf"

Response:

{
  "upload": {
    "token": "6bk3gql82em5nmf",
    "attachment": { "id": 498483, "file_name": "invoice_12345.pdf", ... }
  }
}
  1. Use the upload token in a ticket comment:
curl -X PUT \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": {
      "comment": {
        "body": "Please find your invoice attached.",
        "public": true,
        "uploads": ["6bk3gql82em5nmf"]
      }
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/tickets/98765.json"

Edge cases / pitfalls:

  • Upload tokens expire after 7 days if not attached to a ticket.
  • Maximum attachment size is 50 MB per file by default (account setting may lower this).
  • Multiple files: pass multiple tokens in the uploads array.
  • The upload endpoint accepts any Content-Type header matching the file's MIME type.

Validation: GET the ticket comments and confirm the comment's attachments array contains the file.


Recipe 6: Create and manage webhooks

Goal: Receive real-time notifications when a ticket is marked urgent.

Preconditions: Token with webhooks:write. HTTPS endpoint that can receive POST requests.

Steps:

  1. Create the webhook:
curl -X POST \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook": {
      "name": "Urgent Ticket Alert",
      "endpoint": "https://hooks.example.com/zendesk/urgent",
      "http_method": "POST",
      "request_format": "json",
      "status": "active",
      "subscriptions": ["conditional_ticket_events"]
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/webhooks"
  1. Note the returned webhook.id.
  2. Create a Trigger in Admin Center (or via API) that fires on ticket.priority = urgent and uses the webhook as the action target. Triggers are the event source; the webhook is the delivery mechanism.
  3. Validate delivery: use GET /api/v2/webhooks/{webhook_id}/invocations to inspect recent calls and their response codes.
  4. To rotate the signing secret: PATCH /api/v2/webhooks/{webhook_id}/signing_secret.

Edge cases / pitfalls:

  • Zendesk signs webhook payloads with an HMAC-SHA256 signature in the X-Zendesk-Webhook-Signature header. Always verify this on your receiver.
  • Your endpoint must respond with 2xx within 5 seconds or Zendesk marks the invocation as failed.
  • Zendesk retries failed webhook deliveries with exponential backoff — ensure your endpoint is idempotent.
  • The older "HTTP Target" mechanism is deprecated; use the /api/v2/webhooks endpoint for new integrations.

Validation: Send a test event via Admin Center > Webhooks > Test or by manually triggering the condition. Confirm your endpoint receives the payload and returns 200.


Recipe 7: Bulk ticket update

Goal: Reassign 200 tickets from a departing agent to a new agent.

Preconditions: Token with tickets:write. List of ticket IDs.

Steps:

  1. Collect ticket IDs (via search or incremental export).
  2. PUT to bulk update endpoint (max 100 IDs per request):
curl -X PUT \
  -u "agent@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": {
      "assignee_id": 778899
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/tickets/update_many.json?ids=100,101,102,103"
  1. Response is a Job Status object (async):
{ "job_status": { "id": "8b726e606741012fce25026b5d53c60", "status": "queued", ... } }
  1. Poll job status:
curl -u "agent@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/job_statuses/8b726e606741012fce25026b5d53c60.json"
  1. Repeat until status is completed or failed.

Edge cases / pitfalls:

  • Maximum 100 IDs per update_many request. Batch your list accordingly.
  • Job status polling: wait at least 2–5 seconds between polls to avoid rate limit consumption.
  • If any ticket in the batch fails, the job may still complete partially — check results array in the completed job status for per-ticket success/failure.
  • For historical imports that should bypass triggers, use POST /api/v2/imports/tickets (bulk import) instead.

Validation: Poll job status to completed. Spot-check 5 random ticket IDs from the batch to confirm assignee_id updated correctly.

Operational notes:

  • Use update_many for homogeneous changes like tags, assignee, or status across a known set of ticket IDs.
  • Keep request batches at 100 IDs max and persist the returned job_status_id.
  • If the job partially succeeds, re-run only the failed IDs after inspecting the job result.

Recipe 8: Manage agent permissions and group membership

Goal: Add a new agent to a support group and verify their access.

Preconditions: Admin-level token. Know user_id and group_id.

Steps:

  1. Create a group membership:
curl -X POST \
  -u "admin@example.com/token:TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "group_membership": {
      "user_id": 112233,
      "group_id": 445566
    }
  }' \
  "https://{subdomain}.zendesk.com/api/v2/group_memberships.json"
  1. Optionally set as default group for the user:
curl -X PUT \
  -u "admin@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/group_memberships/{membership_id}/make_default.json"
  1. Verify:
curl -u "admin@example.com/token:TOKEN" \
  "https://{subdomain}.zendesk.com/api/v2/users/112233/group_memberships.json"

Edge cases / pitfalls:

  • Only admins can manage group memberships via API.
  • A user can belong to multiple groups. Ticket routing via Trigger can use group_id to fan-out correctly.
  • Removing an agent from a group does not re-assign their open tickets — do this explicitly.

Validation: List group members and confirm the new user appears: GET /api/v2/groups/445566/memberships.json.


Cross-tool recipe: Zendesk Ticket -> Jira Bug escalation

Goal: Escalate a Zendesk ticket into Jira when support confirms the issue is a product defect.

Trigger examples:

  • Agent adds tag engineering
  • Ticket priority changes to urgent
  • Custom field issue_type becomes bug

Flow:

  1. Capture the Zendesk event via webhook or Incremental Export.
  2. Build a stable external key such as zendesk:{ticket_id}.
  3. Search Jira for an existing issue using that key in a custom field or label.
  4. If no issue exists, create a Jira Bug with the Zendesk URL, customer impact, and reproduction details.
  5. Write the Jira issue key back to Zendesk as an internal note or custom field so agents can track engineering progress.

Minimum field mapping:

Zendesk ticket fieldJira field
subjectsummary
latest public commentdescription
prioritypriority
tagslabels
idexternal ID field / label

Operational guardrails:

  • Make the create path idempotent; webhooks can be retried.
  • Keep status sync one-way at first. Bi-directional sync needs an explicit state map and conflict resolution policy.
  • Post only sanitized customer context into Jira if Zendesk comments may include PII.

Query patterns & filtering

Search API

Endpoint: GET /api/v2/search.json?query={query_string}

Query fragmentMeaning
type:ticketOnly tickets (vs users, orgs)
status:openFilter by status
priority:urgentFilter by priority
tags:billingTickets with tag
organization_id:123By org
assignee:agent@example.comBy assignee email
created>2026-01-01Created after date
updated<2026-02-01Updated before date
fieldvalue:360001234567:billingCustom field value

Combined example:

query=type:ticket+status:open+priority:urgent+tags:billing&sort_by=created_at&sort_order=desc

Pagination

Zendesk supports two pagination styles:

Cursor-based (recommended for large result sets):

GET /api/v2/tickets.json?page[size]=100

Response includes meta.after_cursor. Use it for the next page:

GET /api/v2/tickets.json?page[size]=100&page[after]={after_cursor}

Continue until meta.has_more is false.

Offset-based (legacy):

GET /api/v2/tickets.json?per_page=100&page=2

Max per_page is 100. Max offset pages: 100 (10,000 records). For more, use cursor or Incremental Export.

Incremental Export API (for sync)

GET /api/v2/incremental/tickets.json?start_time=1708300800
  • start_time: Unix epoch seconds. Store end_time from response as next start_time.
  • Returns up to 1000 records per call. Keep calling until count < 1000 (end of stream).
  • Also available for users and organizations: /api/v2/incremental/users.json, /api/v2/incremental/organizations.json.
  • Events include deleted tickets — check status field.

Production sync pattern:

  1. Start with start_time at least 5 minutes in the past.
  2. Continue following the returned window until you have drained the stream for the current run.
  3. Persist the returned end_time, not your request timestamp.
  4. Re-run with a small overlap window to protect against delayed indexing and consumer crashes.
  5. Deduplicate by ticket id and latest updated_at in your sink.

Sorting

Search API: &sort_by=created_at&sort_order=asc (or desc). List endpoints: &sort_by=updated_at&sort_order=desc.

Handling duplicates

  • Tickets are unique by id. When importing or syncing, use external ticket.external_id field to map your system's IDs to Zendesk IDs and detect duplicates.
  • Before creating a ticket from an external event, search by external_id first:
    GET /api/v2/tickets.json?external_id=YOUR_EXTERNAL_ID

Reliability: rate limits, retries, idempotency

Rate limits by plan

PlanRequests per minute
Enterprise700
Professional400
Team200
Essential200
  • Limits apply per API token / OAuth app (not per IP).
  • When exceeded, Zendesk returns HTTP 429 Too Many Requests with a Retry-After header (seconds).

Backoff strategy

import time, requests

def zendesk_request(method, url, **kwargs):
    for attempt in range(5):
        resp = method(url, **kwargs)
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", 60))
            time.sleep(retry_after)
            continue
        resp.raise_for_status()
        return resp
    raise Exception("Exceeded retry budget")

Bulk update and export guardrails

  • update_many is efficient for operational changes across up to 100 tickets, but it is not a substitute for a full sync.
  • Poll the returned job status before assuming the bulk update succeeded.
  • For analytics or warehouse sync, use Incremental Export rather than paginating Search or list endpoints.
  • Keep writes and reads separate: use update_many for operational changes, Incremental Export for system-of-record ingestion.

Retry vs fail-fast

HTTP StatusAction
429 Too Many RequestsRetry after Retry-After seconds
503 Service UnavailableRetry with exponential backoff (max 3 attempts)
500 Internal Server ErrorRetry once; if persistent, alert and fail
422 Unprocessable EntityFail-fast — fix the request payload
404 Not FoundFail-fast — resource does not exist
403 ForbiddenFail-fast — fix permissions
401 UnauthorizedFail-fast — fix credentials

Idempotency & safe-upsert patterns

  • Ticket creation: Use external_id on the ticket. Before POSTing, GET by external_id. If found, skip or update; if not, create.
  • Bulk updates: The update_many endpoint is safe to retry because PUT is idempotent for the same payload.
  • Attachment uploads: Upload tokens are single-use but expire after 7 days. If a ticket POST fails after an upload, re-use the same token in a retry within the window; otherwise re-upload.
  • Webhooks: Your receiving endpoint must be idempotent. Zendesk may deliver the same event more than once. Use the webhook invocation ID or a unique field from the payload as a deduplication key.

Concurrency

  • Run multiple parallel API calls carefully. Each counts against the per-minute limit regardless of parallelism.
  • For bulk jobs, prefer update_many (single API call for 100 tickets) over N parallel single-ticket PUTs.

Error handling & troubleshooting

Common error codes

CodeMeaningResolution
400 Bad RequestMalformed JSON or missing required fieldCheck JSON syntax; inspect description in response body
401 UnauthorizedInvalid or missing credentialsVerify email/token encoding; check token not revoked
403 ForbiddenAuthenticated but insufficient permissionsCheck OAuth scopes or agent role
404 Not FoundResource does not existConfirm ID; check subdomain
409 ConflictDuplicate resource (e.g., duplicate external_id)Handle as upsert; fetch existing record
422 Unprocessable EntityValidation failureRead details array in response for field-level errors
429 Too Many RequestsRate limit exceededRespect Retry-After header
500 Internal Server ErrorZendesk-side errorRetry once; check Zendesk status page
503 Service UnavailableMaintenance or overloadRetry with backoff; check status.zendesk.com

"If you see X, do Y" playbook

  • 422 with "Field: Status is not included in the list": You are passing an invalid status value. Check the allowed values for your plan (some plans do not have on-hold).
  • 404 on a ticket you just created: Allow a few seconds for indexing. Retry with a short delay.
  • Attachments not appearing on ticket: Confirm the upload token was used in the same API call that created/updated the ticket comment. Tokens cannot be added retroactively to existing comments.
  • Webhook not firing: Verify the Trigger condition matches the ticket data exactly. Check invocations endpoint for delivery errors. Confirm the webhook status is active.
  • Bulk update job stuck in queued: Jobs can take minutes under load. Poll every 10 seconds for up to 5 minutes. If still queued, check job status API for error messages.

Debug logging recommendations

  • Log: request method, URL (excluding token), request body (redact PII), HTTP status, response body on non-2xx, Retry-After value, job status ID for async calls.
  • Use Zendesk's own audit log (Admin Center > Account > Audit log) to verify API-driven changes from the Zendesk side.
  • For webhooks, log the X-Zendesk-Webhook-Id and X-Zendesk-Webhook-Invocation-Id headers for correlation.

Security, privacy, compliance

PII / PHI considerations

  • Customer names, email addresses, phone numbers, and ticket bodies may constitute PII under GDPR, CCPA, and HIPAA.
  • Do not log raw ticket bodies in application logs. Log ticket IDs and metadata only.
  • Use Zendesk's Redaction API (PUT /api/v2/tickets/{id}/comments/{comment_id}/redact) to permanently remove PII from comments when legally required.
  • For HIPAA-covered entities: Zendesk offers a BAA on Enterprise plans. Confirm your plan and BAA status before storing PHI.

Data minimization

  • When reading tickets for reporting, use ?include= only for the related objects you actually need.
  • In webhook payloads, configure the payload template to include only the fields needed by the downstream system.
  • Do not store Zendesk API tokens or OAuth secrets in data warehouses or logs.

Audit trails

  • Zendesk maintains an audit log of all API changes accessible via Admin Center > Account > Audit log and the Audit Log API: GET /api/v2/audit_logs.json.
  • Every ticket update creates an Audit record visible in the ticket's audit history: GET /api/v2/tickets/{id}/audits.json.
  • Use created_by_id on audit records to distinguish user-driven vs. API-driven changes.

Admin vs user-level tokens

  • Admin token: Full access including user management, configuration, and audit logs. Use only for administrative automation.
  • Agent token: Scoped to ticket operations permitted by the agent's role. Prefer for day-to-day automation.
  • End-user tokens: Not applicable for API integrations. End users interact via the Help Center portal or email.
  • Create a dedicated Zendesk agent account (service account) for each integration. Do not use a human agent's credentials for automation.

Testing checklist

  • Auth test: Confirm API token returns 200 on GET /api/v2/users/me.json. Confirm a wrong token returns 401.
  • CRUD test: Create a ticket, read it back by ID, update subject and status, add a public comment, add an internal note, confirm all fields persisted correctly.
  • Pagination test: Fetch a list endpoint with page[size]=2 and walk through at least 3 pages using page[after] cursor. Confirm no records are skipped or duplicated.
  • Rate limit test: Send requests in a tight loop until you receive a 429. Confirm Retry-After header is present. Confirm your retry logic waits the correct number of seconds and then succeeds.
  • Webhook test: Create a webhook, trigger the associated condition, and confirm your endpoint receives the payload. Verify the HMAC-SHA256 signature. Confirm idempotent re-delivery handling.
  • Permission test: Use an agent-scoped token to attempt an admin-only action (e.g., delete a user). Confirm 403 Forbidden is returned.
  • Negative tests: Send a POST with a missing required field — confirm 422. Send a GET for a non-existent ticket ID — confirm 404. Send a bulk update with 101 IDs — confirm appropriate error.

Sources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.