Connect Zendesk
Tasks call Zendesk Support through qbash.integrations.zendesk with a credential saved on the Connectors page. A credential connects one Zendesk account through Zendesk's own sign-in, and tasks act as the agent or admin who signed in. A task sees the tickets, groups and views that user can see. Its notes and ticket changes appear in Zendesk under that user's name.
After you connect Zendesk, you can add its tools to a qbash MCP server for ChatGPT, Claude and other MCP clients, or call it from a Python task. The Zendesk MCP server page lists what the connector can do.
Choose the account
Connect as a Zendesk agent or admin. qbash checks the role before it saves the credential and refuses an end user's sign-in.
A dedicated agent account named for the integration makes the notes and changes a task writes easy to tell apart from your team's. The credential also keeps working when people leave.
Agents can use every mode within the groups and tickets they can see. Two reads depend on the role or the plan:
get_routing_optionsreturns queues and brands only to an admin, and skills only on accounts with skills-based routing. A section it can't read comes back empty, andunavailablegives Zendesk's reason.list_side_conversationsneeds Zendesk's Collaboration add-on or a plan that includes it.
qbash asks Zendesk for two scopes: read for every read mode, and tickets:write for internal notes and ticket updates.
Connect in qbash
- Open Connectors, then Zendesk, and click Connect.
- Enter a credential name and your Zendesk subdomain, the part of your Zendesk address before
.zendesk.com. Forhttps://acme.zendesk.com, enteracme. If your help center uses its own domain, still enter the zendesk.com subdomain, because qbash can't connect through a custom domain. - If your account limits access by IP address, select Requires static egress IP. Then add the addresses shown in the form to the allowed ranges on the IP restrictions tab, under Account → Security → Advanced in Zendesk Admin Center. Zendesk refuses API calls from addresses outside those ranges.
- Click Connect with Zendesk. In the Zendesk window that opens, sign in as the agent or admin you chose and allow the access qbash requests. qbash confirms the user's role and saves the credential.
- In the task editor, open Resources → Integrations and select this credential. See Connect resources.
qbash renews the connection before it expires, so scheduled tasks keep running without anyone signing in again. If Zendesk stops accepting it, for example after the token is revoked, qbash marks the credential as needing reconnection. Reconnect on the credential opens the Zendesk sign-in again for the same subdomain.
To connect another Zendesk account, add another credential. Editing a credential changes its name or its static egress setting, and the subdomain stays the one it was connected with.
Read a ticket and its conversation
zd = qbash.integrations.zendesk
ticket = zd.get_ticket(ticket_id=12345)["ticket"]
print(ticket["id"], ticket.get("subject"), ticket["status"], ticket["url"])
thread = zd.list_ticket_comments(ticket_id=12345)
for comment in thread["comments"]:
print(comment["created_at"], comment["visibility"], comment.get("author_name"))
print(comment.get("body", ""))
get_ticket returns the ticket's status, routing fields, tags, custom field values and a url that opens the ticket in the agent workspace. It leaves out Zendesk's description field, which repeats the first comment and can contain an internal note. Keys whose value is null are left out too, so read optional fields with .get().
list_ticket_comments returns public replies and internal notes, oldest first. Each comment and each attachment has a visibility of public or internal, so a task can keep internal notes out of anything it sends to the requester. Pass visibility="public" to read only what the requester can see. A long conversation comes in pages: pass next_cursor back as cursor until it is None.
download_attachment copies an attachment into qbash file storage and returns a file reference to pass to a prompt or to a connector mode that takes files. It refuses files over 50 MB and files Zendesk flagged as malware.
To find tickets, search_tickets takes Zendesk search syntax such as status<solved priority:urgent, and list_view_tickets reads a view in the view's own order. get_user and get_organization read the requester and their company. Their details and notes fields are for agents only. list_ticket_audits shows who changed which field and through which channel, and list_side_conversations reads a ticket's threads with other teams.
Before a task writes, have it read the account's own values. list_custom_fields returns field ids and dropdown options. get_routing_options reads the valid statuses, groups, forms, brands, skills and queues from several Zendesk endpoints, so call it once per run. list_assignable_agents lists the agents who can take tickets in a group.
Pick up changed tickets on a schedule
zd = qbash.integrations.zendesk
cursor = qbash.cache.get("zendesk-cursor")
changes = zd.list_ticket_changes(cursor=cursor, limit=50)
for ticket in changes["tickets"]:
print(ticket["id"], ticket["status"], ticket.get("subject"))
qbash.cache.set("zendesk-cursor", changes["next_cursor"], "30d")
Without a cursor, list_ticket_changes starts 24 hours back. Pass since with an ISO 8601 time to start somewhere else, up to 30 days back. Tickets come oldest first. next_cursor is always set, and storing it after the tickets are handled means a run that fails reads them again next time. When has_more is True, more changes are waiting and the next call reads them.
The call leaves out tickets whose only new changes came from this credential's update_ticket or add_internal_note calls, so a task that updates tickets doesn't pick up its own work on the next run. It counts them in skipped_own_changes. The check goes by credential, so it also leaves out changes another task made with the same credential. It costs one Zendesk request per changed ticket. Pass exclude_own_changes=False to get every changed ticket.
Zendesk events can't start a task yet, so run intake on a schedule like this.
Update a ticket and add an internal note
zd = qbash.integrations.zendesk
ticket = zd.get_ticket(ticket_id=12345)["ticket"]
result = zd.update_ticket(
ticket_id=12345,
priority="high",
custom_fields={"Jira issue": "SUP-142"},
add_tags=["escalated"],
internal_note="Escalated to engineering as SUP-142.",
expected_updated_at=ticket["updated_at"],
dedupe_key="escalation:SUP-142",
)
print(result["changed_fields"], result["note_added"], result["skipped"])
Only the parameters you pass change. Before sending anything, qbash checks each value against the account's current settings: statuses and custom statuses, the groups the connected user can assign to, the agents in the target group, dropdown options and skill values. When nothing would change, nothing is sent and changed is False.
custom_fields maps a field id, or a field's exact title, to its new value. None clears a field, and dropdowns take an option's value or name. To link a ticket to an engineering issue, store the issue key in a text field as the example does, and find the ticket later with search_tickets(query="custom_field_<field id>:SUP-142").
update_ticket reads the ticket before it writes, and Zendesk refuses the write with a 409 conflict if the ticket changes in between. Pass expected_updated_at, the updated_at from your task's own read, to also refuse the update when the ticket changed after that read. Read the ticket again before retrying.
dedupe_key skips the write when a change with the same key is among the ticket's latest 100 audits, so a retried run doesn't add a second note. The result then has skipped set to True. add_internal_note takes the same key.
Notes are always internal. Agents see them, and the requester and CCs get no notification. The connector has no mode for public replies, and it doesn't create, merge or delete tickets.
The SDK Reference lists every method and parameter.
Prepare test data
Connect a Zendesk trial account first, so test notes and updates stay out of customer tickets. Then set up:
- a few tickets in different statuses, created in the agent workspace or by emailing your support address
- one ticket with a public reply, an internal note and an attachment, so
list_ticket_commentsreturns both visibilities - a text ticket field for engineering keys and a dropdown field, whose ids
list_custom_fieldsreturns - a second group with an agent in it, for testing assignment
Run each write against one test ticket first. Calling update_ticket twice with the same dedupe_key should return skipped=True the second time.
Details that affect results
- Zendesk's search index lags new changes by a few minutes, so
search_ticketscan miss a ticket changed moments ago. A query returns at most 1,000 results, so narrow the query rather than paging past that. list_ticket_changesholds back changes from the last 15 seconds until the next call, and it never returns deleted tickets. One call reads at most 2,000 changed tickets. Beyond that it skips the oldest and setstruncated.- A cursor or
sincemore than 30 days old is refused. The example's cache entry expires after 30 days for that reason, and the next run then starts 24 hours back. - Zendesk allows 30 updates to one ticket per user every 10 minutes. qbash retries a rate-limited request, up to three attempts in all, when Zendesk asks for a wait of 30 seconds or less. A longer wait returns an error that names it.
- A 403 means the connected user lacks the role, permission or plan feature a call needs, and the credential stays connected. Only a connection that Zendesk rejects marks the credential for reconnection.
- qbash deletes attachments saved by
download_attachment7 days after the download. It also clears the inputs and results of Zendesk steps from run history after 30 days, keeping each step's name, status and timing. - Help Center, Talk, Chat, Messaging and Sell are outside the connector, as are satisfaction ratings, ticket metrics and macros. No mode uploads a file to a ticket.