Pricing

How to Connect Zendesk to Google Sheets

ZendeskSaved SearchDateDocumentAmount07/01INV-40214,280.0007/02INV-40221,860.5007/05INV-40239,140.0007/08INV-40242,375.2507/09INV-40256,902.7507/12INV-40263,118.40BrookedABCDateDocumentAmount123456707/01INV-40214,280.0007/02INV-40221,860.5007/05INV-40239,140.0007/08INV-40242,375.2507/09INV-40256,902.7507/12INV-40263,118.40Google SheetsZendesk report
JW
James Whitfield

Export Zendesk tickets to Google Sheets, including the /token suffix that fixes 401s and the offset pagination that stops dead at 10,000 records.

Zendesk's API is capable and well documented, and two specific things account for most of the time people lose with it. Authentication needs a /token suffix on the username that is easy to miss and produces an unhelpful 401, and the familiar offset pagination stops dead at 10,000 records, which is a wall any established help desk hits. This guide handles both, plus the incremental export endpoint built for exactly that volume problem.

The built-in option: Zendesk Explore

Explore is Zendesk's own reporting tool and can export any report to CSV or Excel. If your question is a standard support metric, first response time, resolution time, volume by channel, building it in Explore and exporting is quicker than anything involving the API. Scheduled delivery of those reports is limited to higher plans, and getting the output into Sheets still means importing a file.

Zendesk to Google Sheets for free (Apps Script + REST API)

Enable token access under Admin Center → Apps and integrations → APIs → Zendesk API and create an API token. Your subdomain is the first part of your Zendesk URL.

The authentication format is the thing to get right. Zendesk expects basic auth where the username is [email protected]/token, literally with /token appended, and the password is the API token. Omit the suffix and you get a 401 that looks like a wrong token.

For pagination, use the cursor style rather than page=N offsets. Offset paging is capped at 10,000 records and errors beyond it, whereas cursor paging with page[size] and links.next has no such ceiling.

Code.gs
function importZendeskTickets() {  const SUBDOMAIN = "yourcompany";  const EMAIL = "[email protected]";  const TOKEN = "YOUR_API_TOKEN";   // The /token suffix is required. Without it this 401s with a valid token.  const auth = "Basic " + Utilities.base64Encode(EMAIL + "/token:" + TOKEN);  const headers = { Authorization: auth };  const root = "https://" + SUBDOMAIN + ".zendesk.com/api/v2";   // Custom fields come back as numeric IDs. Map them to titles once.  const fieldTitles = {};  JSON.parse(UrlFetchApp.fetch(root + "/ticket_fields.json", { headers })    .getContentText()).ticket_fields.forEach((f) => {      fieldTitles[f.id] = f.title;    });   const rows = [];  // Cursor pagination. Offset paging (page=N) breaks past 10,000 records.  let url = root + "/tickets.json?page[size]=100";   while (url) {    const response = UrlFetchApp.fetch(url, { headers, muteHttpExceptions: true });    if (response.getResponseCode() !== 200) {      throw new Error(response.getResponseCode() + ": " + response.getContentText());    }     const body = JSON.parse(response.getContentText());     body.tickets.forEach((t) => {      const custom = (t.custom_fields || [])        .filter((f) => f.value !== null && f.value !== "")        .map((f) => (fieldTitles[f.id] || f.id) + ": " + f.value)        .join("; ");       rows.push([        t.id,        t.subject,        t.status,        t.priority || "",        t.via ? t.via.channel : "",       // email, chat, web, api…        t.created_at,        t.updated_at,        (t.tags || []).join(", "),        custom,      ]);    });     // meta.has_more tells you whether to follow links.next.    url = (body.meta && body.meta.has_more) ? body.links.next : null;    if (url) Utilities.sleep(300);  }   const header = ["ID","Subject","Status","Priority","Channel",                  "Created","Updated","Tags","Custom fields"];  const ss = SpreadsheetApp.getActiveSpreadsheet();  const sheet = ss.getSheetByName("Tickets") || ss.insertSheet("Tickets");  sheet.clearContents();  sheet.getRange(1, 1, 1, header.length).setValues([header]);  if (rows.length) {    sheet.getRange(2, 1, rows.length, header.length).setValues(rows);  }}

For full history on an established help desk, switch to the incremental export endpoint at /api/v2/incremental/tickets.json with a start_time. It returns up to 1,000 tickets per page plus an end_time to feed into the next run, so each scheduled import only fetches what changed. It is rate limited to roughly 10 requests a minute, which is the trade for its bulk capability.

Limits: Apps Script stops at six minutes, and Zendesk's rate limits vary by plan, from a few hundred requests a minute upward. Ticket comments and satisfaction ratings need separate calls per ticket, which is impractical at volume without the incremental endpoints.

The Brooked method

Brooked authenticates correctly, uses cursor pagination so the 10,000 record ceiling never applies, and resolves custom field IDs and dropdown tag values to their display names. Tickets, users, organizations and satisfaction ratings can all land in one spreadsheet on a single schedule.

Step 1: Install Brooked

Install Brooked from the Google Workspace Marketplace. Free tier includes 100 imports per month, no credit card required.

Step 2: Connect Zendesk

Choose Zendesk in Brooked, enter your subdomain, the email address of an agent account, and an API token. Then pick tickets, users or organizations, optionally filter by view or date range, and import.

Step 3: Schedule the refresh

Set the import to re-run hourly or daily. Support tickets change state constantly, so a scheduled re-read keeps status, assignee and resolution columns accurate rather than frozen at the moment each ticket was created.

BrookedBrooked AI, Zendesk
Live
Ask anything about your Zendesk data…

Method comparison

MethodCostSetupRefreshTwo-wayBest for
Zendesk Explore exportIncluded on most plans5 to 15 minScheduled delivery on higher plansNoTeams already building reports in Explore
Apps Script + REST APIFree25 to 40 minTime-driven triggerDIYCustom ticket exports, no budget
Incremental export APIFree30 to 45 minTime-driven triggerNoLarge volumes and full history
BrookedFree tier · Pro $29/user/mo~3 minScheduled (hourly, daily, weekly)YesScheduled support reporting in Sheets

Common use cases

  • Support KPIs: first response and resolution times by channel, team or priority
  • SLA monitoring: find tickets approaching or past their target and alert on them
  • Volume forecasting: ticket counts by week to plan staffing
  • Product feedback: group tickets by tag to find what keeps generating contacts
  • Account health: join ticket volume against revenue data already in the sheet

Troubleshooting common issues

Frequently asked questions

Why does Zendesk return 401 with a valid API token?

Almost always the missing /token suffix. Zendesk expects the basic auth username to be your email address followed by /token, as in [email protected]/token, with the API token as the password. Using just the email address fails even when the token is correct, and the error message does not hint at the cause.

Why does my export stop at 10,000 tickets?

Offset-based pagination on Zendesk's search and list endpoints is limited to 10,000 records, and requests past that return an error rather than more data. Use cursor pagination with page[size] and follow links.next, or for very large volumes use the incremental export endpoint, which is designed for full history.

Can I export Zendesk tickets to Google Sheets for free?

Yes. The REST API is available on all plans and the Apps Script method here uses a free API token and a free time-driven trigger. Zendesk Explore also exports reports to CSV, though scheduled delivery of those reports is limited to higher plans. Brooked's free tier covers 100 imports a month.

What is the incremental export endpoint for?

It is built for syncing large volumes and full history without paging through everything each time. You pass a start_time and get up to 1,000 tickets per page along with an end_time to use as the next start, so each run only fetches what changed. It is rate limited more tightly, around 10 requests per minute, in exchange for that bulk capability.

How do custom fields come back?

As a custom_fields array on each ticket, with numeric field IDs and values rather than names. Call the ticket_fields endpoint once to build an ID-to-title map, then translate. Dropdown fields return the option's tag value rather than its display label, which also needs mapping from the field definition.

Can I update Zendesk tickets from Google Sheets?

Yes with a write-capable token, and Brooked supports write-back for editable fields such as status, priority, assignee and tags. Be deliberate about it: bulk status changes trigger triggers and automations in Zendesk, which can fire notification emails to customers.

Connect Zendesk to Google Sheets today.

Free tier: 100 imports per month, no credit card required. Or see the full Zendesk integration details.

Install Brooked free →

Get your spreadsheet hours back

Brooked sets up in seconds. Your team is querying live data before lunch.

Get started free