Closing stale tickets with the Tickets API

Keeping your Zendesk environment organized and efficient often requires routine housekeeping, especially as your support team grows or changes. One common support operation is closing out tickets that have become stale and inactive.

Closing stale tickets helps to keep your Zendesk support environment organized and your reporting accurate. Old tickets that have been inactive for a long period can unnecessarily inflate your backlog and distort key metrics. Programmatically closing these tickets, especially those with no recent activity, helps maintain accurate workload visibility and supports ticket lifecycle compliance.

While Zendesk’s Admin Center supports automatic closure using automation rules, using the API lets you customize the closure workflow.

This article shows you how to use the Zendesk Tickets API to bulk-close stale tickets that have been idle for too long.

What you need

You'll need an API token from a Zendesk account. The API token must be created by an admin in the Zendesk account to ensure the token has sufficient API permissions.

You also need to install Python and the following packages if you don't already have them:

Setting your environment variables

Gather your Zendesk user email, API token, and Zendesk subdomain such as the string "mondocam" in "https://mondocam.zendesk.com".

To set the environment variables

  1. In your project folder, create a text file named .env (with a leading period).

  2. Use a text editor to paste the environment variables into the file:

    ZENDESK_SUBDOMAIN=your_subdomainZENDESK_USER_EMAIL=your_email@example.comZENDESK_API_TOKEN=your_api_token

    Note that the values don't use quotes.

  3. Save the updated .env file in your project folder.

  4. If you're using GitHub for this project, add .env to your .gitignore file so it doesn't get pushed to the remote branch where others could access it.

Closing stale tickets

In your project folder, create a file named close_stale_tickets.py and paste the following Python script into it.

import osimport requestsimport arrowfrom dotenv import load_dotenv
# set configuration settingDAYS_OLD = 365  # close tickets not updated in the last DAYS_OLD days
# load Zendesk credentialsload_dotenv()   # reads variables from the .env file and sets them in os.environ
# set global variablesBASE_URL = f"https://{os.environ['ZENDESK_SUBDOMAIN']}.zendesk.com"AUTH = f"{os.environ['ZENDESK_USER_EMAIL']}/token", os.environ['ZENDESK_API_TOKEN']

def find_stale_tickets() -> list:    """    Finds all tickets that are open, pending, or on hold    (not solved/closed) and haven't been updated in over DAYS_OLD days.    Returns a list of ticket IDs to close.    """    stale_ticket_ids = []
    # get the cutoff time    cutoff_time = arrow.utcnow().shift(days=-DAYS_OLD).format('YYYY-MM-DDTHH:mm:ss') + 'Z'
    # get the stale tickets with the API    url = f"{BASE_URL}/api/v2/search.json"    query = f"type:ticket status<solved updated<{cutoff_time}"    params = {'query': query, 'per_page': 100}    response = requests.get(url, auth=AUTH, params=params)    while url:        if response.status_code != 200:            raise requests.HTTPError(f"{response.status_code}: {response.reason}")        data = response.json()        for ticket in data['results']:            stale_ticket_ids.append(ticket['id'])        url = data['next_page']        if url:            response = requests.get(url, auth=AUTH)
    return stale_ticket_ids

def close_ticket(ticket_id):    """    Sets the status of a ticket to 'solved'.    Zendesk will automatically close tickets after they're marked solved.    Adds a public comment for visibility.    """    url = f"{BASE_URL}/api/v2/tickets/{ticket_id}.json"    payload = {        'ticket': {            'status': 'solved',            'comment': {                'public': True,                'body': f"The stale ticket script automatically closed this ticket due to inactivity (> {DAYS_OLD} days)."            }        }    }    response = requests.put(url, auth=AUTH, json=payload)    if response.status_code == 200:        print(f"Closed ticket {ticket_id}")    else:        print(f"Failed to close ticket {ticket_id}: {response.status_code} {response.text}")

def main():    ticket_ids = find_stale_tickets()    print(f"Found {len(ticket_ids)} stale tickets to close.")    for ticket_id in ticket_ids:        close_ticket(ticket_id)

if __name__ == "__main__":    main()

How it works

The script automatically detects and closes tickets that have not been updated within a configured number of days, and adds a public comment to each affected ticket.

The script begins by searching for tickets that are open, pending, or on hold (not solved or closed) and haven’t been updated in more than DAYS_OLD days.

For each stale ticket found, the script updates its status to “solved” and adds a public comment explaining the closure due to inactivity. After a set period, Zendesk automatically transitions the solved tickets to “closed.”

Each solved ticket is reported in the command-line output.

Example:

Found 21 stale tickets.Closed ticket 86Closed ticket 85Closed ticket 84Closed ticket 83...

To run the script, navigate to your project folder with your command line interface, then run python3 close_stale_tickets.py.