Users can subscribe to (or follow) a section in the knowledge base, but the help center doesn't list the section followers. This tutorial describes how to use the API to list the followers of a specified section in your knowledge base. Subjects like sideloading and pagination are covered along the way.

The scripting language used is Python, a powerful but beginner-friendly programming language with a clear and readable syntax. If you work in another language, you should still be able to follow the code logic and adapt it to your script. The logic is similar in all languages.

The API requests in the tutorial are authenticated using an API token. Therefore, the script should only be used internally. If you want to make the functionality more widely available, you should switch to OAuth authentication.

Disclaimer: Zendesk provides this article for instructional purposes only. Zendesk does not support or guarantee the code. Zendesk also can't provide support for third-party technologies such as Python or Perl.

What you need

You need a text editor and a command-line interface like the command prompt in Windows or the Terminal on the Mac. You'll also need Python 3.2 or greater and a special library to make HTTP requests.

To set up your development environment:

  1. If you don't already have Python 3, download and install it from http://www.python.org/download/. Python is a powerful but beginner-friendly scripting and programming language with a clear and readable syntax. Visit the Python website to learn more.

  2. If you have Python 3.2 or 3.3, download and install pip if you don't already have it. pip is a simple tool for installing and managing Python packages. See these instructions.

    Note: If you have Python 3.4 or better, you already have pip. Skip ahead.

  3. Use the following pip command in your command-line interface to download and install the Requests library, a library that makes HTTP requests in Python easy:

    $ pip3 install requests

    Note: The dollar sign ($) represents the command prompt.

    If you have Python 3.3 or earlier, use pip instead of pip3 on the command line.

  4. Finally, when copying the examples in this tutorial, make sure to indent lines exactly as shown. Indentation matters in Python.

If you're interested in taking a deeper dive into Python after finishing this tutorial, see the following free resources:

The plan

The plan is to create a script that lets a user specify a section id on the command line and lists all the followers when the user hits Enter. Example:

$ python3 list_followers.py 234304094>> Chuck Brown>> George Lukas>> Attila Hon...

The subscriptions API gets the followers of a section. However, the API returns user ids, not actual user names. You'll also need to get the names from the users API. To avoid unnecessary API requests, you can sideload the users with the subscriptions. Sideloading lets you download two or more recordsets in a single request.

The subscriptions API only returns 30 subscriptions at a time, so the script also needs to paginate through all the results to make sure it doesn't skip anyone.

Here are the script's basic tasks:

Get the section id from the user

  1. In a text editor, create a text file and save it as list_followers.py.

  2. Enter the following lines at the top of the file:

    import argparse
    parser = argparse.ArgumentParser()parser.add_argument("id")

    The script imports the built-in argparse module, which provides access to command-line arguments. The script then creates an ArgumentParser object and defines one command-line argument named id.

  3. Grab the argument value the user entered on the command line and assign it to a variable:

    args = parser.parse_args()hc_id = args.id

If all goes well, the user should enter a section id on the command line after the script name. Example:

$ python3 list_followers.py 123456789

If the user forgot to specify an id, the script displays an error message and exits:

$ python3 list_followers.pyusage: list_followers.py [-h] idlist_followers.py: error: the following arguments are required: id

Make the request

The next step is to configure the API requests. You'll use the requests library, a third-party Python library for making HTTP requests. You should have installed it earlier. See What you need.

  1. Import the requests library at the top of the file:

    import requests...
  2. Create a requests Session object and configure it with your authentication information:

    session = requests.Session()session.headers = {'Content-Type': 'application/json'}session.auth = ('your_user_email/token', 'your_api_token')

    The Session object is useful for making multiple requests, such as when paginating through API results.

    The session.auth property specifies your Zendesk Support sign-in email and API token. Replace your_user_email and your_api_token with your information, making sure '/token' stays appended to your email. You can grab an API token from the Zendesk Admin Center interface at Apps and integrations > APIs > Zendesk APIs.

    Note: You should only use the script internally with an API token. If you want to make it more widely accessible, you should switch to OAuth authentication.

  3. Construct the url for the request:

    zendesk = 'https://your_subdomain.zendesk.com'endpoint = '/api/v2/help_center/sections/{}/subscriptions.json'url = zendesk + endpoint.format(hc_id)

    The endpoint is listed as follows in the Subscriptions API doc:

    GET /api/v2/help_center/sections/{section_id}/subscriptions.json

    The script combines your Zendesk Support url with the endpoint url. Replace your_subdomain with your information. The format() method replaces the {} placeholder in the url with the value of the hc_id variable.

  4. Make the API request and assign the response to a variable:

    response = session.get(url)
  5. Check the response for errors and exit if any are found:

    if response.status_code != 200:    print('Failed to get followers with error {}'.format(response.status_code))    exit()

    According to the API doc, the API returns a status code of 200 if the request is successful. In other words, if the status code is anything other than 200 (if response.status_code != 200), then something went wrong. The script prints an error message and exits.

  6. If no errors are found, decode the JSON response into a Python data structure and assign it to a variable. The line should be flush with the left margin.

    data = response.json()

    The Zendesk API returns data formatted as JSON. The json() method from the requests library decodes the JSON into the equivalent Python data structure to make it easier to manipulate.

Consult the Zendesk API docs to figure out how the response data is structured in Python. For example, according to the subscriptions API doc, the JSON returned by the API has the following structure:

{  "subscriptions": [    {      "id":          35467,      "user_id":     888887,      "source_type": "Section",      ...    },    ...  ]}

You can deduce from the doc that the decoded data is a Python dictionary with one key named subscriptions. Its value is a list of subscriptions, as indicated by the square brackets. Each item in the list is a dictionary of subscription properties, as indicated by the curly braces.

A dictionary is simply a set of key/value pairs formatted almost identically to JSON. Example:

{'id': 35436, 'user_id': 88887}

Equipped with this knowledge of the data's structure, you can add the following temporary snippet to check the results so far:

for subscription in data['subscriptions']:    print(subscription['user_id'])

The snippet iterates through the value of the 'subscriptions' key (a list of subscriptions) and prints the value of the 'user_id' key of each subscription.

You can do a test run on the command line as follows, replacing the section id with one of your own:

$ python3 list_followers.py 234567891

When you're done testing, delete the test snippet from the script.

As you probably noticed, the subscriptions API only returns the user id, not the user name. You'll need to get the names from the users API, as described in the next section.

Sideloading users

Because the subscriptions API returns user ids, not actual user names, you'll need to get the names from the users API. To avoid unnecessary API calls, you can sideload the users along with the subscriptions. Sideloading lets you download both recordsets in a single request.

Where previously the response data consisted of a dictionary with a single 'subscriptions' key, the updated data will have an additional key named 'users' containing a list of all the users mentioned in the subscriptions:

{  'subscriptions': [    {      'user_id': 7,      ...    },    ...  ],  'users': [    {      'id': 7,      'name': 'Bob Bobberson',      ...    }  ]}

Critically, sideloading doesn't get all the users in your Zendesk Support instance. It gets only the users specified in the subscriptions. So listing a section's followers becomes a simple matter of listing the names of the sideloaded users.

Adapt your script as follows to list the followers.

  1. To sideload the users, add an ?include=users parameter to the endpoint url:

    endpoint = '.../subscriptions.json?include=users'
  2. At the end of the script, print the names of the section's followers:

    for user in data['users']:    print(user['name'])

The script will list the users of the first 30 subscriptions and stop. In the next section, you'll check for more results and paginate through them to make sure you get every follower.

Paginate through the results

For bandwidth reasons, the API doesn't return large record sets all at once. It breaks up the results into smaller subsets and returns them in pages. The number of items per page varies by endpoint. The subscriptions endpoint returns 30 items per page.

After each request, the script should check to see if more results exist. The JSON response contains a next_page property with the URL of the next page of results, if any. Example:

"subscriptions": [ ... ],"next_page": "https://example.zendesk.com/api/v2/help_center/sections/112233445/subscriptions.json?page=2",...

If there's no next page, the value is null:

"subscriptions": [ ... ],"next_page": null,...

With this information, the script can paginate through the results.

  1. Just before script makes the request, create a list variable to cumulatively store the users found while paginating through the results:

    users = []...
  2. Indent the request block under a new while statement, as follows:

    users = []while url:    response = session.get(url)    if response.status_code != 200:        print('Failed ...')        exit()    data = response.json()

    The block runs while url has a value. It stops running when url is null, such as when the last page is reached.

  3. After getting data in a response, add the list of users to the cumulative total:

    ...    if data['subscriptions']:        users.extend(data['users'])

    First, make sure to check the initial request returned at least one subscription. If the section doesn't have any subscriptions, then the data['users'] variable won't exist and you'll get an error when you try to use it.

    The extend() method in Python adds the contents of one list (in this case data['users']) to another list (the users list you declared before the loop).

  4. Get the url of the next page of results:

    ...    url = data['next_page']

    If the value is null, which Python considers false, the script exits the loop.

  5. In the for loop that prints the followers, change the list variable from data['users'], which contains only a subset of users, to users, which contains the cumulative list of all the users:

    for user in users:    print(user['name'])

After the updates, the final part of the script should look like this (ignore the line breaks cause by the margins):

...users = []while url:    response = session.get(url)    if response.status_code != 200:        print('Failed to get followers with error {}'.format(response.status_code))        exit()    data = response.json()    if data['subscriptions']:        users.extend(data['users'])    url = data['next_page']
for user in users:    print(user['name'])

Code complete

import argparseimport requests

parser = argparse.ArgumentParser()parser.add_argument("id")args = parser.parse_args()hc_id = args.id
session = requests.Session()session.headers = {'Content-Type': 'application/json'}session.auth = ('your_zd_email/token', 'your_api_token')zendesk = 'https://your_subdomain.zendesk.com'endpoint = '/api/v2/help_center/sections/{}/subscriptions.json?include=users'url = zendesk + endpoint.format(hc_id)
users = []while url:    response = session.get(url)    if response.status_code != 200:        print('Failed to get followers with error {}'.format(response.status_code))        exit()    data = response.json()    if data['subscriptions']:        users.extend(data['users'])    url = data['next_page']
for user in users:    print(user['name'])

Feature enhancement: Get article followers

As well as sections, users can also follow articles in your help center. You can modify the script to list article followers too. Users can include a command-line flag like -a or -s to specify whether the id they enter on the command line refers to an article or section. Example:

$ list_followers.py -a 998877665

To upgrade the script to support article followers:

  1. Add two new arguments to your parser object near the top of the file, after creating the object but before parsing the args:

    ...parser.add_argument('-s', action='store_true')parser.add_argument('-a', action='store_true')...

    If the user specifies one of the arguments, the script assigns it the value of True. Not specifying it implies False.

  2. In the section that builds the request url, replace the endpoint variable declaration with the following if block (ignore the line breaks):

    if args.a:    endpoint = '/api/v2/help_center/articles/{}/subscriptions.json?include=users'else:    endpoint = '/api/v2/help_center/sections/{}/subscriptions.json?include=users'

    If the user specifies the -a flag, the script uses the list article subscriptions endpoint. By default if the user doesn't specify a flag, the script uses the list section subscriptions endpoint.

To learn more, see the Argparse tutorial on the Python website.

Appendix: Using Perl

You can create and run the script with Perl. The syntax is slightly different but the logic is the same. See the previous sections if something in the flow isn't clear.

To run the script, you'll need to install Perl on your computer if you don't already have it. If you use a Mac, Perl is probably already installed. Run the following command in Terminal to find out: perl -v.

If you don't have Perl or have a version older than 5.10 on Mac or Windows, see http://www.perl.org/get.html to download and install the latest stable version. On the Mac, Perlbrew (perlbrew.pl) is a useful tool for managing Perl installations.

After installing Perl, download and install the following modules if you don't already have them:

See these instructions to install the modules. The LWP modules are for communicating with the REST API. The JSON module is for reading and converting the JSON data returned by the API.

Here's one way to write the script. Make sure to replace your_zd_email, your_api_token, and your_subdomain with your information.

use strict;use warnings;use LWP::UserAgent;use JSON;use MIME::Base64;
my $num_args = $#ARGV + 1;if ($num_args != 1) {    print "Usage error: list_followers.pl hc_id\n";    exit;}
my $zendesk = 'https://your_subdomain.zendesk.com';my $endpoint = "/api/v2/help_center/sections/$ARGV[0]/subscriptions.json?include=users";my $url = $zendesk . $endpoint;
my $credentials = encode_base64('your_zd_email/token:your_api_token');my $ua = LWP::UserAgent->new(ssl_opts =>{ verify_hostname => 0 });
my @users;while ($url) {    my $response = $ua->get($url, 'Authorization' => "Basic $credentials");    die 'http status: ' . $response->code . '  ' . $response->message        unless ($response->is_success);    my $data = decode_json($response->content);    push @users, @{ $data->{'users'} };
    if (defined $data->{'next_page'}) {        $url = $data->{'next_page'};    } else {        $url = '';    }}
foreach my $user ( @users ) {    print $user->{"name"}  . "\n";}

If you're interested in learning Perl for your projects, see the following resources: