Importing users with the Zendesk API

Have more questions? Submit a request

26 Comments

  • Michael Jessen
    Comment actions Permalink

    This is great!

    I'm trying to make this work, though, and hitting a lot of stumbling blocks. We're using Duo remote authentication, so I need to use an API token. How would I integrate that here?

    I tried changing the session.auth to:

    session.auth = 'emailaddress/token:token'

    But now I'm getting a lot more errors on output that suggest to me this isn't right.

    Traceback (most recent call last):
    File "user_import.py", line 33, in
    response = session.post(url, data=payload)
    File "/Library/Python/2.7/site-packages/requests/sessions.py", line 508, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
    File "/Library/Python/2.7/site-packages/requests/sessions.py", line 451, in request
    prep = self.prepare_request(req)
    File "/Library/Python/2.7/site-packages/requests/sessions.py", line 382, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
    File "/Library/Python/2.7/site-packages/requests/models.py", line 308, in prepare
    self.prepare_auth(auth, url)
    File "/Library/Python/2.7/site-packages/requests/models.py", line 496, in prepare_auth
    r = auth(self)
    TypeError: 'str' object is not callable

    0
  • Charles Nadeau
    Comment actions Permalink

    Hi Michael,

    session.auth should be a tuple, so try

    `session.auth = 'emailaddress/token', 'token'`

    Make sure the string '/token' remains appended to your email.

    0
  • Michael Jessen
    Comment actions Permalink

    Hi Charles,

    Yikes! I had actually done that first, but must have made some other error, because I thought that didn't work. But it does, so thank you!

    That's made all the other errors go away, but it does still fail with "status 400".

    I had also received the message "InsecurePlatformWarning: A true SSLContext object is not available" but was able to solve that with "pip install requests[security]", but perhaps that had an unintended consequence?

    Thank you for the help!

    0
  • Charles Nadeau
    Comment actions Permalink

    A 400 error usually means the request couldn't be understood by the server due to some syntax problem. Can you make a successful test request with curl? Something like:

    ```
    curl https://{subdomain}.zendesk.com/api/v2/users/create_many.json \
    -H "Content-Type: application/json" -X POST \
    -d '{"users": [{"name": "Roger Wilco", "email": "roge@example.org"}, {"name": "Woger Rilco", "email": "woge@example.org"}]}' \
    -v -u {email_address}:{password}
    ```

    As for the SSL problem, I notice you're using Python 2.7. I haven't tested with that version (I'm using 3.3) but from a quick Google search, upgrading to 2.7.9 might help.

    0
  • Michael Jessen
    Comment actions Permalink

    I can, yes!

    I have to remove the brackets around email_address and password for it to work. I'm very new to Python, so a lot of this is new to me. But THAT script does create a user. Is there something I can learn from that success to get the other one working?

    I was just running through OS X terminal, which is 2.7, but I've also tried on 3.4.3 as well. Same thing!

    0
  • Charles Nadeau
    Comment actions Permalink

    Hi Michael

    It seems to point to a problem with the data pulled from your spreadsheet and sent in the request. The sample script assumes some text data in column C, D, and H of a spreadsheet. The `print` statement is your best friend here for debugging. You can inspect the data you're trying to send. If you switched to Python 3, add the following print statement in the `for payload in payloads` loop and comment out the rest of the lines:

    ```
    for payload in payloads:
    print(payload)
    # response = session.post(url, data=payload)
    # if response.status_code != 200:
    # print('Import failed with status {}'.format(response.status_code))
    # exit()
    # print('Successfully imported a batch of users')
    ```

    If you're using Python 2.7, the print statement is `print payload`.

    Anything look strange in the JSON that's printed out compared to what's in your spreadsheet? The data will be different but it should be formed like the data in the curl test request:

    ```
    {"users": [{"name": "Roger Wilco", "email": "roge@example.org"}, {"name": "Woger Rilco", "email": "woge@example.org"}]}
    ```

    0
  • Michael Jessen
    Comment actions Permalink

    Wow, thank you so much. I really appreciate your walking me through this. I had added phone number as a column, and Excel was formatting it as a number, so even though it looked right, the script was trying to feed in xxxx.0!

    0
  • Winterrain Sa
    Comment actions Permalink

    hi,
    is there any way to use this in php

    0
  • Jordan Weekes
    Comment actions Permalink

    There is a incorrect section on this page which caused us a few hours headache before we figured it out! Otherwise good job! :)


     


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

    Should be


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

    0
  • Charles Nadeau
    Comment actions Permalink

    Sorry about that. I fixed the typo. Thanks.  

    0
  • John Warner
    Comment actions Permalink

    The above code which i modified slightly worked fine for importing all my users, but now when I ran it again the first batch of 100 users fails with a status code of 400.


    Could someone from Zendesk support let me know what could be the cause of this error?


    Thanks

    0
  • John Warner
    Comment actions Permalink

    Nevermind, I had changed the POST to a PUT and that was borking it up.

    0
  • Ben Heiligenthal
    Comment actions Permalink

    I'm working on a script that will continually run to CREATE OR UPDATE MANY users. My script was working fine, but now we want to include all email addresses, not just the primary. I found the "identities" property, and was able to get it to work for a single users just using the CREATE OR UPDATE API. Now, when I try to add it to my MANY script I receive the error:

    Invoke-RestMethod : { "error": { "title": "Invalid attribute", "message": "You passed an invalid value for the identities attribute. Invalid parameter: identities must be a hash" } }

     

    I'm confused because I used identities as an array with a single user and it worked, but now it seems to expects a different format for the MANY call. Can someone give me the format for how to use identities in a CREATE OR UPDATE MANY API request?

     

    0
  • Christopher Cooper
    Comment actions Permalink

    Hi Ben,

    I was able to successfully use our Create Or Update Many Users API while including multiple email identities for the users. In my testing, the first identity in the array becomes the primary identity. Here is an example of the payload I used:

    curl https://{subdomain}.zendesk.com/api/v2/users/create_or_update_many.json \
    -d '{"users": [{"name": "user1", "identities": [{ "type": "email", "value": "user1_email1@gmail.com"}, {"type": "email", "value": "user1_email2@gmail.com" }]}, {"name": "user2", "identities": [{ "type": "email", "value": "user2_email1@gmail.com"}, {"type": "email", "value": "user2_email2@gmail.com" }]}]}' \
    -H "Content-Type: application/json" -X POST \
    -v -u {email_address}:{password}

    I hope this helps. Please let me know if you need additional assistance with this.

    0
  • Ben Heiligenthal
    Comment actions Permalink

    Thanks Christopher,

     

    I realized my mistake, when converting to JSON it had a default depth of 3, so the identities was getting cut off. After I set the Depth to 5 things worked!

     

    Thanks for the help

    0
  • Gary Kester
    Comment actions Permalink

    Hi Charles,

    Any chance you have a version of this that would work with a csv?

    Regards,

    Gary

    0
  • Charles Nadeau
    Comment actions Permalink

    I don't have a dedicated article but you could easily modify the code in this one to work with csv files. Python comes with it's own native csv library so you don't need to install anything else.

    The following possible rewrite of the code in the article is very back-of-the-envelope and not tested but it should point you in the right direction.

    import csv
    
    users_dict = {'users': []}
    
    users_file = 'users.csv'
    with users_file as f:
        reader = csv.reader(f)
        for row in reader:
            if row[2]:
                users_dict['users'].append(
                    {
                        'name': row[2],
                        'email': row[3],
                        'user_fields': {'member_level': row[7]}
                    }
                )

    More info on the csv module at  https://docs.python.org/3/library/csv.html.

     

    0
  • Gary Kester
    Comment actions Permalink

    That works perfectly thanks.

    I need to install the requests module to proceed with the test I'm doing

    I have several user fields that I update and I amended the script as follows:

    (should my user fields by a subset of 'user_fields' or is it ok like this?

    import csv, json, requests

    users_file = "users_short.csv"
    json_users_file = "users.json"

    payloads = []
    users_dict = {'users': []}

    with open(users_file) as f:
    reader = csv.reader(f)
    for row in reader:

    if row[0]:
    users_dict['users'].append(

    {
    'name': row[0],
    'email': row[1],
    'external_id': row[2],
    'details': row[3],
    'notes': row[4],
    'phone': row[5],
    'role': row[6],
    'restriction': row[7],
    'organization': row[8],
    'tags': row[9],
    'custom_fields.Username': row[10],
    'custom_fields.User_number': row[11],
    'custom_fields.dob': row[12],
    # 'user_fields': {'member_level': row[7]}

     

    0
  • Charles Nadeau
    Comment actions Permalink

    Hi Gary,

    It's ok like this (with 'user_fields' commented out). The 'user_fields' object represents any custom fields you define for the user profiles in Zendesk. See User Fields and Adding custom fields to users. The property is completely optional. You'd define these extra fields in the Zendesk admin before importing your users if the default user fields in Zendesk don't match one or more fields in your external user record.

     

    0
  • Gary Kester
    Comment actions Permalink

    Thanks

    It fails error code 400 if I add the additional fields.

    Note I'm using create_update_many but get the same result for both.

     

    I also want to use token authentication instead of leaving my password in the script. The user guides notes that it need to be encoded into a base64 format but there are not examples of how the code would be added to the script above or how to encode from the token generated in Zendesk admin.

    I've removed my email, password and domain from the script below

     

    import csv, json, requests

    users_file = 'users_test.csv'

    session = requests.Session()
    session.headers = {'Content-Type': 'application/json'}
    session.auth = 'your_zd_email', 'your_zd_password'
    url = 'https://your_subdomain.zendesk.com/api/v2/users/create_or_update_many.json'

    payloads = []
    users_dict = {'users': []}

    with open(users_file) as f:
    reader = csv.reader(f)
    for row in reader:
    if row[0]:
    users_dict['users'].append(
    {
    'name': row[0],
    'email': row[1],
    'external_id': row[2],
    'notes': row[4],
    'phone': row[5],
    'role': row[6],
    'restriction': row[7],
    'organization': row[8],
    'tags': row[9],
    'custom_fields.Username': row[10],
    'custom_fields.User_number': row[11],
    'custom_fields.dob': row[12]
    }
    )

    if len(users_dict['users']) == 100:
    payloads.append(json.dumps(users_dict))
    users_dict = {'users': []}

    if users_dict['users']:
    payloads.append(json.dumps(users_dict))

    for payload in payloads:
    response = session.post(url, data=payload)
    if response.status_code != 200:
    print('Import failed with status {}'.format(response.status_code))
    exit()
    print('Successfully imported a batch of users')

     

     

    0
  • Charles Nadeau
    Comment actions Permalink

    If you imported the requests library into your script, it'll take care of the base64 encoding for you. Also, if you're using a Zendesk API token, make sure to append the string "/token" (the string, not the token itself) to your email address.

    session.auth = 'your_zd_email/token', 'your_zd_api_token'
    0
  • Gary Kester
    Comment actions Permalink

    Thanks Charles. 

    Token authentication works like a charm now.

     

    None of my custom fields import though?

     

    'custom_fields.Username': row[10],
    'custom_fields.User_number': row[11],
    'custom_fields.dob': row[12]

     

    I found that 'organization': row[8], expects organization_id as an integer  - I've commented this out and can update basic info

    Regards,

    Gary

     

    0
  • Charles Nadeau
    Comment actions Permalink

    Python doesn't use dot notation for dictionaries. Try:

    'user_fields': {
    'Username': row[10],
    'User_number': row[11],
    'dob': row[12]

    }

    Make sure to define these fields in the Zendesk admin before you attempt to populate them.

    Also, you can use int() to cast a string to an integer (assuming the string only consists of numbers):

    'organization': int(row[8]),
    0
  • Gary Kester
    Comment actions Permalink

    Thanks Charles. 

    The custom fields all work perfectly and I found that the field key is case sensitive so making these lower case fixed it.

    The organization is actually a text string and not that critical at this point in time.

    =-=--=-=-=-=-=-=

    import csv, json, requests

     

    users_file = 'users.csv'

    session = requests.Session()
    session.headers = {'Content-Type': 'application/json'}
    session.auth = 'zendeskemaillogin/token', 'apitokengeneratedthroughadmin'
    url = 'https://businesssubdomain.zendesk.com/api/v2/users/create_or_update_many.json'

    payloads = []
    users_dict = {'users': []}

    with open(users_file) as f:
          reader = csv.reader(f)
          for row in reader:
          if row[0]:
                 users_dict['users'].append(
                     {
                        'name': row[0],
                        'email': row[1],
                        'external_id': row[2],
                        'notes': row[2],
                        'phone': row[5],
                        'role': 'end-user',
                        'restriction': row[7],

                        'tags': row[9],
                        'verified': True,
                        'user_fields': {

    #customer user fields - use "Field key" - is case sensitive
                                'username': row[10],
                                'user_number': row[11],
                                'dob': row[12]}
    }
    )

    if len(users_dict['users']) == 100:
             payloads.append(json.dumps(users_dict))
             users_dict = {'users': []}

    if users_dict['users']:
             payloads.append(json.dumps(users_dict))

    for payload in payloads:
             response = session.post(url, data=payload)
             if response.status_code != 200:
                  print('Import failed with status {}'.format(response.status_code))
                  exit()
             print('Successfully imported a batch of users')

    0
  • Yann Hervé-Bazin
    Comment actions Permalink

    Hi,

    Is it normal to receive a 403 error when running this "API POST /api/v2/users/create_many.json" on a Trial Account ?

    0
  • Bryan - Community Manager
    Comment actions Permalink

    Yes Yann Hervé-Bazin -- this API is disabled on trial accounts.

    0

Please sign in to leave a comment.

Powered by Zendesk