# Zendesk Client

Create tickets, manage users, and interact with Zendesk's customer support platform.

## Methods

| Method                                   | Description                                         |
| ---------------------------------------- | --------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Zendesk API request with schema validation |

## Usage

### Create a Ticket

```typescript
import { api, z, zendesk } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_ZENDESK = "a1b2c3d4-5678-90ab-cdef-zendesk00001";

const TicketSchema = z.object({
  id: z.number(),
  url: z.string(),
  subject: z.string(),
  description: z.string(),
  status: z.string(), // new, open, pending, hold, solved, closed
  priority: z.string().nullable(), // low, normal, high, urgent
  requester_id: z.number(),
  assignee_id: z.number().nullable(),
  created_at: z.string(),
  updated_at: z.string(),
  tags: z.array(z.string()),
});

const CreateTicketResponseSchema = z.object({
  ticket: TicketSchema,
});

export default api({
  integrations: {
    zendesk: zendesk(PROD_ZENDESK),
  },
  name: "ZendeskExample",
  input: z.object({
    subject: z.string(),
    description: z.string(),
    requesterEmail: z.string().email(),
  }),
  output: z.object({
    ticketId: z.number(),
  }),
  async run(ctx, { subject, description, requesterEmail }) {
    const result = await ctx.integrations.zendesk.apiRequest(
      {
        method: "POST",
        path: "/api/v2/tickets.json",
        body: {
          ticket: {
            subject: subject,
            comment: { body: description },
            requester: { email: requesterEmail },
            priority: "normal",
            tags: ["api-created"],
          },
        },
      },
      { response: CreateTicketResponseSchema },
    );

    return { ticketId: result.ticket.id };
  },
});
```

### Get a Ticket

```typescript
const GetTicketResponseSchema = z.object({
  ticket: TicketSchema,
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "GET",
    path: `/api/v2/tickets/${ticketId}.json`,
  },
  { response: GetTicketResponseSchema },
);

console.log(`Ticket #${result.ticket.id}: ${result.ticket.subject}`);
```

### Search Tickets

```typescript
const SearchResponseSchema = z.object({
  results: z.array(TicketSchema),
  count: z.number(),
  next_page: z.string().nullable(),
  previous_page: z.string().nullable(),
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "GET",
    path: "/api/v2/search.json",
    params: {
      query: "type:ticket status:open priority:high",
      sort_by: "created_at",
      sort_order: "desc",
    },
  },
  { response: SearchResponseSchema },
);

result.results.forEach((ticket) => {
  console.log(`#${ticket.id}: ${ticket.subject} (${ticket.status})`);
});
```

### Update a Ticket

```typescript
await ctx.integrations.zendesk.apiRequest(
  {
    method: "PUT",
    path: `/api/v2/tickets/${ticketId}.json`,
    body: {
      ticket: {
        status: "pending",
        priority: "high",
        tags: ["escalated"],
        comment: {
          body: "Escalating this ticket to the engineering team.",
          public: false, // Internal note
        },
      },
    },
  },
  { response: GetTicketResponseSchema },
);
```

### Add a Comment

```typescript
await ctx.integrations.zendesk.apiRequest(
  {
    method: "PUT",
    path: `/api/v2/tickets/${ticketId}.json`,
    body: {
      ticket: {
        comment: {
          body: "Thank you for your patience. We are working on this.",
          public: true, // Public reply
        },
      },
    },
  },
  { response: GetTicketResponseSchema },
);
```

### Create a User

```typescript
const UserSchema = z.object({
  id: z.number(),
  url: z.string(),
  name: z.string(),
  email: z.string(),
  role: z.string(), // end-user, agent, admin
  created_at: z.string(),
  updated_at: z.string(),
});

const CreateUserResponseSchema = z.object({
  user: UserSchema,
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "POST",
    path: "/api/v2/users.json",
    body: {
      user: {
        name: "John Doe",
        email: "john@example.com",
        role: "end-user",
        verified: true,
      },
    },
  },
  { response: CreateUserResponseSchema },
);
```

### List Tickets for a User

```typescript
const ListTicketsResponseSchema = z.object({
  tickets: z.array(TicketSchema),
  next_page: z.string().nullable(),
  count: z.number(),
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "GET",
    path: `/api/v2/users/${userId}/tickets/requested.json`,
    params: {
      sort_by: "created_at",
      sort_order: "desc",
    },
  },
  { response: ListTicketsResponseSchema },
);
```

### Get Ticket Comments

```typescript
const CommentSchema = z.object({
  id: z.number(),
  body: z.string(),
  author_id: z.number(),
  public: z.boolean(),
  created_at: z.string(),
});

const ListCommentsResponseSchema = z.object({
  comments: z.array(CommentSchema),
  next_page: z.string().nullable(),
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "GET",
    path: `/api/v2/tickets/${ticketId}/comments.json`,
  },
  { response: ListCommentsResponseSchema },
);

result.comments.forEach((comment) => {
  console.log(`${comment.public ? "Public" : "Internal"}: ${comment.body}`);
});
```

### Bulk Update Tickets

```typescript
const BulkUpdateResponseSchema = z.object({
  job_status: z.object({
    id: z.string(),
    url: z.string(),
    status: z.string(),
  }),
});

const result = await ctx.integrations.zendesk.apiRequest(
  {
    method: "PUT",
    path: "/api/v2/tickets/update_many.json",
    params: {
      ids: "1,2,3,4,5",
    },
    body: {
      ticket: {
        status: "solved",
        tags: ["bulk-closed"],
      },
    },
  },
  { response: BulkUpdateResponseSchema },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await zendesk.createTicket({ ... });
await zendesk.searchTickets({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.zendesk.apiRequest(
  { method: "POST", path: "/api/v2/tickets.json", body: { ticket: { ... } } },
  { response: CreateTicketResponseSchema }
);
```

### Wrap Objects in Parent Key

Zendesk wraps objects in a parent key:

```typescript
// WRONG - No wrapper
const body = {
  subject: "Help!",
  comment: { body: "I need help" },
};

// CORRECT - Wrapped in "ticket"
const body = {
  ticket: {
    subject: "Help!",
    comment: { body: "I need help" },
  },
};

// Same for users
const userBody = {
  user: {
    name: "John Doe",
    email: "john@example.com",
  },
};
```

### .json Suffix in Paths

Zendesk paths end with `.json`:

```typescript
// WRONG - Missing .json
const path = "/api/v2/tickets";

// CORRECT - Include .json
const path = "/api/v2/tickets.json";
```

### Search Query Syntax

```typescript
// Type filters
const query = "type:ticket";
const query = "type:user";

// Status filters
const query = "type:ticket status:open";
const query = "type:ticket status<solved"; // new, open, pending

// Date filters
const query = "type:ticket created>2024-01-01";

// Priority
const query = "type:ticket priority:high";

// Assignee
const query = "type:ticket assignee:none";
const query = "type:ticket assignee:me";

// Tags
const query = "type:ticket tags:vip";

// Text search
const query = 'type:ticket "login issue"';

// Combine multiple
const query = "type:ticket status:open priority:urgent created>2024-01-01";
```

### Comments via Update

Comments are added by updating the ticket:

```typescript
// Can't POST to /tickets/:id/comments directly
// Instead, PUT to /tickets/:id.json with comment in body

const body = {
  ticket: {
    comment: {
      body: "Your comment here",
      public: true, // Public reply vs internal note
    },
  },
};
```

### Pagination

Zendesk uses offset-based pagination:

```typescript
async function getAllTickets(zendesk: ZendeskClient) {
  const allTickets: Ticket[] = [];
  let page = 1;

  while (true) {
    const result = await ctx.integrations.zendesk.apiRequest(
      {
        method: "GET",
        path: "/api/v2/tickets.json",
        params: { page, per_page: 100 },
      },
      { response: ListTicketsResponseSchema },
    );

    allTickets.push(...result.tickets);
    if (!result.next_page) break;
    page++;
  }

  return allTickets;
}
```

### Rate Limits

Zendesk has rate limits (varies by plan):

```typescript
// Check X-Rate-Limit and X-Rate-Limit-Remaining headers
// Default: 400 requests per minute for most endpoints
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.zendesk.apiRequest(
    { method: "POST", path: "/api/v2/tickets.json", body: { ... } },
    { response: CreateTicketResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Zendesk API Documentation](https://developer.zendesk.com/api-reference/)
- [Tickets](https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/)
- [Users](https://developer.zendesk.com/api-reference/ticketing/users/users/)
- [Search](https://developer.zendesk.com/api-reference/ticketing/ticket-management/search/)
