# Zendesk (/docs/workflows/integrations/zendesk)

> For the complete documentation index, see [llms.txt](https://docs.tandemcx.ai/llms.txt). Any page on this site can be fetched as Markdown by appending `.md` to its URL.

The Zendesk integration lets a workflow or an AI agent file a ticket in your Zendesk
Support account: capturing what a caller reported, handing an unresolved conversation to
a human team, or logging a request the agent could not complete.

Tandem connects using **your own Zendesk OAuth client**, so you control which Zendesk
account it reaches and what the token is allowed to do. Creating that client is a
one-time setup, described below.

You can connect more than one — a sandbox and a production account, or one per brand.
Each is named, and the node picks which to file in.

## Setup

### 1. Create an OAuth client in Zendesk

In Zendesk, go to **Admin Center → Apps and integrations → APIs → OAuth clients** and
click **Add OAuth client**.

- **Client kind** must be **Confidential**. Zendesk refuses the grant Tandem uses from a
  public client.
- **Unique identifier** is auto-filled from the name. Keep it or change it — this is the
  value you paste into Tandem.
- **Redirect URLs** can be left as-is. Tandem never redirects a browser to Zendesk.

### 2. Copy the secret

Zendesk generates the secret when you save, and shows it in full **exactly once**.
Afterwards only its first nine characters are visible.

<Callout type="warn">
    **The secret is longer than the box it appears in.** Select all of it before copying — a
    truncated paste looks like a normal value and fails only when Tandem first asks Zendesk for a
    token.
</Callout>

### 3. Connect it in Tandem

On the **Integrations** page, add a Zendesk integration and fill in:

| Field                 | What to enter                                                              |
| --------------------- | -------------------------------------------------------------------------- |
| **Name**              | How you'll recognize this account in the node's picker.                    |
| **Subdomain**         | Just the subdomain. For `https://acme.zendesk.com`, enter `acme`.          |
| **Unique Identifier** | The value from the OAuth client you just created.                          |
| **Secret**            | The secret you copied.                                                     |
| **Scopes**            | What the token is allowed to do. Defaults to `tickets:read tickets:write`. |

Tandem asks Zendesk for a token before saving, so a wrong subdomain, identifier, secret,
or scope string is rejected immediately rather than surfacing later as a failed ticket.

<Callout>
    Add `users:write` to **Scopes** if you file tickets for a requester by email. Zendesk matches an
    existing end user on that address and **creates one** when there is no match, and creating a
    user is a write some accounts allow only a token that carries it.
</Callout>

<Callout>
    Add `users:read` to **Scopes** to list your Zendesk end-users on the **Contacts** page (`read`
    covers it too). To add it to an existing integration, open the integration, add it under
    **Scopes**, and save. If your OAuth client in Zendesk limits which scopes it allows, allow
    `users:read` there as well, or saving fails with `Zendesk rejected the requested scopes`. The
    client's user in Zendesk must be an agent or an admin. See [Your Zendesk
    users](/docs/agents/contacts#your-zendesk-users).
</Callout>

## What you can do

- **File a ticket** — set a subject and a description, and pick which connected Zendesk
  account to file in.
- **Build the ticket from the flow** — the subject and description are `$(variable)`
  interpolated, so they can carry caller details and results from earlier nodes.
- **Attach it to a person** — set **Requester Email** and the ticket lands in that
  person's name. Leave it blank and the ticket is filed as the integration itself.
- **File an internal note instead** — turn **Visible to Requester** off and the
  description is filed as a note your agents can see and the customer cannot.
- **Set priority, type, and tags** — or leave them blank to inherit your Zendesk
  account's own defaults.
- **Route it** — **Assignee Email** assigns the ticket to one of your Zendesk agents.
  **Assignee ID**, **Group ID**, and **Brand ID** do the same by numeric id, for a group
  queue or a multi-brand account. Leave them blank and your Zendesk triggers route the
  ticket.
- **Write your own custom fields** — by Zendesk field id.
- **Capture the result** — **Save Ticket Number To** and **Save Ticket Link To** put the
  new ticket's number and its agent-facing link into variables for later nodes.

## Giving it to an AI agent

There are two ways to let an agent reach Zendesk, and they suit different jobs.

### As an integration tool

On an agent's **Tools**, add a tool of kind **Integration** and pick your Zendesk account
from the target list — connected Zendesk accounts appear there under a **Zendesk** heading,
alongside your generic integrations. Picking one swaps the method and path fields for a
single **Action** dropdown:

| Action            | What the agent does                                                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Create Ticket** | Files a ticket. It supplies the subject, the description, the requester's name and email, and optionally a priority, type, and tags. |
| **Show Ticket**   | Reads a ticket back by its number, so the agent can tell a caller its status, subject, priority, and when it was last updated.       |

You configure the tool's **Name** and **Description** — Tandem builds the request, so there is
no method, path, or parameter list to fill in. The description is what the model reads to
decide when to reach for the tool, so write it for that: _"Files a support ticket when the
caller reports a problem you cannot resolve on the call."_

**Create Ticket** also takes **Assignee Email**, **Assignee ID**, **Group ID**, and **Brand
ID**. You set them on the tool, not the model: an agent or brand id belongs to your Zendesk,
and a model asked for one can only guess. Every ticket the tool files uses the values you set.

**Show Ticket** needs `tickets:read`, which the default scopes already include.

### As a node tool

The **Create Zendesk Ticket** node can also be added as a tool. Reach for it instead of the
integration tool when you need the fields the integration tool deliberately does not offer
the model — **custom fields**, **External ID**, and **Visible to Requester**. You fix those
yourself and the agent fills in the rest. Routing is available on both.

<Callout type="warn">
    **Filing a ticket is not repeatable.** If a tool call times out, the ticket may still have been
    created — and an agent's natural next move is to try again, which files a second one. With the
    node tool, set **External ID** to something that identifies the conversation, so a duplicate is
    findable in Zendesk rather than invisible.
</Callout>

## Limits and defaults

- Tandem asks Zendesk for a **one-hour** access token and re-requests one when it expires.
  Nothing about this is visible in the app; you never have to refresh anything by hand.
- Rotating the secret in Zendesk means updating it on the integration. Until you do, every
  ticket fails with a rejected-credential error.
- Changing the subdomain, identifier, secret, or scopes on the integration invalidates the
  current token and asks Zendesk for a new one on save.
- A ticket carries at most **500** tags and **500** custom fields, and a multi-select custom
  field at most **100** values. A ticket over any of these is rejected before it reaches Zendesk.

## Troubleshooting

| What you see                                 | What it means                                                                                             |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Zendesk rejected this OAuth client           | The identifier or secret is wrong, or the client was deleted. Re-enter both.                              |
| No Zendesk account answers at that subdomain | The subdomain is wrong. Enter only the label, not the full URL.                                           |
| The Zendesk token is not allowed to do this  | A missing scope. Add it under **Scopes** and save — ticket creation needs `tickets:write`.                |
| This Zendesk integration can't read users    | The **Contacts** page's Zendesk view needs `users:read`. Add it under **Scopes** and save.                |
| Zendesk rejected the requested scopes        | Your OAuth client in Zendesk doesn't allow a scope listed under **Scopes**. Allow it on the client.       |
| Zendesk rejected the ticket                  | A field value Zendesk refused. The message carries Zendesk's own reason, including a bad custom field id. |
| Zendesk is rate-limiting this account        | Too many requests. It resolves on its own; retry shortly.                                                 |

<Callout>
    Use **Test Connection** on the integration to check the stored OAuth client at any time. It asks
    Zendesk for a fresh token rather than trusting the one Tandem already holds, so it answers "does
    this still work now" — including after someone rotated the secret in Zendesk.
</Callout>

## Nodes

The **Create Zendesk Ticket** node is rolling out; its reference page appears here once it
is generally available.

<IntegrationNodes provider="zendesk" />
