Building a custom API from scratch with legacy custom objects
Note: There is a new custom objects experience. See the Custom Object APIs.
You can use legacy custom objects to build a custom API for a product or service. You can then use the bespoke API to add functionality to a company's web and mobile applications, as well as the agent interface in Zendesk Support if the company has an account.
This tutorial describes how to build a custom API with Zendesk legacy custom objects for a fictional company named DoggoGo.
The completed source code and API documentation for the Doggos API is available on GitHub.
Note: Zendesk provides this article for instructional purposes only. Zendesk does not provide support for the content. Please post any issue in the Zendesk APIs community, or search for a solution online.
DoggoGo requirements
The first step is studying the requirements. DoggoGo.com (Doggos On the Go!) is a walk-sharing service that provides on-demand and scheduled dog walks for pet owners. Customers request walks for their dogs using the DoggoGo application on a mobile device or in a web browser. Dog walkers use the mobile app to accept and schedule walks.
DoggoGo uses Zendesk Support to provide customer service to pet owners. The DoggoGo customer service team would like their agents to have the following additional information about each pet owner's dog:
- Name
- Breed
- Birthday
- Medical conditions, if any
The information will help agents provide better customer service when a pet owner reports a problem.
In addition, dog walkers have requested the following information about each dog:
- Obedience training (yes or no)
- Temperament (calm, confident, energetic, aggressive, couch potato)
- Attitude with strangers (affectionate, reserved, fearful)
You propose creating a Doggos API for the company using legacy custom objects. The API will let the company create, update, and retrieve data about each pet owner's dog. The company could then use the Doggos API for the following tasks:
- Collect dog information from pet owners from the DoggoGo application
- Provide dog information to dog walkers from the DoggoGo mobile app
- Provide their customer service agents with information about each pet owner's dog from a Zendesk app in their Zendesk Support account
System setup
If you just want to follow along with the tutorial and don't want to build the custom API yourself, you can skip this section. The completed source code and docs for the Doggos API is available on GitHub.
If you want to build the custom API, you'll need the following:
-
Zendesk Support
You'll need access to a Zendesk Support account on a Zendesk Suite plan. You can get a free, 15-day trial account. If you're interested in becoming a Zendesk developer partner, you can convert your trial account into a sponsored Zendesk Support account.
You'll also need agent or admin permissions in the account to use the Custom Objects API.
-
Legacy custom objects enabled in the account
Legacy custom objects must be enabled by an administrator in Zendesk Support. If you're not an admin, ask one to enable them for you. To enable legacy custom objects, start by clicking the Zendesk Products icon () in the top bar, then select Admin Center on the lower side of the pop-up box. In Admin Center, click Objects and rules in the sidebar, then select Custom objects > Legacy objects. Finally, on the Legacy objects page, click Activate Custom Objects.
-
Python 3.6 or higher
The custom API is built using Python version 3.6 or higher. To install the latest version, see http://www.python.org/download/.
-
Requests library
Download and install the Requests library for Python if you don't already have it. The Requests library simplifies making HTTP requests in Python. To install it, run the following command in the Terminal on the Mac or the command prompt in Windows:
$ pip3 install requests
-
Bottle framework
Bottle is a minimalist web framework for building web applications with Python. You'll use it in this tutorial to build and test the Doggos API endpoints. Bottle includes a built-in development server that you can use to test your changes locally. To install Bottle, run the following command:
$ pip3 install bottle
Tasks
The DoggoGo project consists of the following tasks:
-
Define a legacy custom object type named doggo to represent the pet owners' dogs. You'll use the doggo object type to create records of the dogs.
-
Create a one-to-many relationship between Zendesk user records and doggo records. A pet owner may have more than one dog.
-
Build an API to create, retrieve, and update doggo records.
-
Document the new API for DoggoGo developers.
Define a doggo legacy object type
You can use the Legacy Custom Objects API to define a new doggo object type, then create doggo records from the new object type.
At its most basic, a legacy object type consists of a key and a JSON schema that describes the data. The key is the name you want to use to identify the object type.
Based on the project requirements, each dog is represented as an object with the following properties:
Name | Type | Mandatory | Comment |
---|---|---|---|
name | string | yes | The dog's name |
breed | string | no | Dog breed |
birthday | string | no | Known or estimated birthday in the YYYY-MM-DD format |
conditions | string | no | Medical conditions, if any |
training | boolean | no | Whether the dog received obedience training |
temperament | string | no | Natural predisposition. Possible values: "calm", "confident", "energetic", "aggressive", "couch potato" |
strangers | string | no | Attitude with strangers. Possible values: "affectionate", "reserved", "fearful" |
These details make up your schema. Notice that the schema doesn't contain any information about a specific dog. It just describes that information.
To create the legacy object type, make a POST request to the following endpoint:
POST /api/sunshine/objects/types
For details, see Create Object Type in the API docs.
Try it yourself
-
Create a project folder in your user path.
Example:
$ jdoe/projects/doggogo
Avoid creating the folder in a shared network path such as Google Drive or Microsoft OneDrive. The Python interpreter can have trouble finding modules in these paths.
-
Use your favorite code editor to create a text file named zendesk.py in your project folder.
The file will act as a Python module for your API application. It'll consolidate all the Zendesk code in one place.
-
Paste the following code into the file:
import requests
import os
ZENDESK_API_TOKEN = os.getenv('ZENDESK_API_TOKEN')
ZENDESK_USER_EMAIL = os.getenv('ZENDESK_EMAIL')
ZENDESK_SUBDOMAIN = 'https://{YOUR_SUBDOMAIN}.zendesk.com'
auth = f'{ZENDESK_EMAIL}/token', ZENDESK_API_TOKEN
def create_object_type(key, schema):
data = {'data': {'key': key, 'schema': schema}}
url = f'{ZENDESK_SUBDOMAIN}/api/sunshine/objects/types'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
response = requests.post(url, json=data, auth=auth, headers=headers)
if response.status_code != 201:
print(f'\nFailed to create {key} object type with error {response.status_code}: {response.text}')
return False
return response.json()['data']
The
create_object_type()
function takes a key and a schema (defined below) and makes a POST request to theapi/sunshine/objects/types
endpoint. -
Replace the placeholders in the ZENDESK_API_TOKEN, ZENDESK_USER_EMAIL, ZENDESK_SUBDOMAIN variables with your information. These are stored as environment variables for security reasons.
-
Create a text file named create_doggo_type.py in your project folder.
-
Paste the following code into the create_doggo_type.py file:
import json
import zendesk
key = 'doggo'
schema = {
'properties': {
'name': {
'type': 'string',
'description': 'The dog\'s name'
},
'breed': {
'type': 'string',
'description': 'Dog breed'
},
'birthday': {
'type': 'string',
'description': 'Known or estimated birthday in the YYYY-MM-DD format. Example: "2015-04-16"'
},
'conditions': {
'type': 'string',
'description': 'Medical conditions, if any'
},
'training': {
'type': 'boolean',
'description': 'Whether the dog received obedience training'
},
},
'required': ['name']
}
response = sunshine.create_object_type(key, schema)
if response:
print(f'\nSuccessfully created the {key} object type.\n')
print(json.dumps(response, indent=2, sort_keys=True) + '\n')
The one-off script provides a schema to define a doggo object type. The schema of a legacy object type is based on the JSON Schema. To learn more, see Creating a schema for a legacy custom object.
The schema validates submitted values. The script imports the zendesk module you created and passes the key and schema to the module's
create_object_type()
function:response = zendesk.create_object_type(key, schema)
-
Save both files.
-
In your command line interface, navigate to your project folder (the one that contains zendesk.py and create_doggo_type.py), then run the following command:
$ python3 create_doggo_type.py
If successful, the console should display the doggo type.
Define a relationship between pet owners and their dogs
The next step is to create a one-to-many relationship between Zendesk user records and doggo records. It's a one-to-many relationship because a pet owner may have more than one dog.
In your project requirements, the pet owner of each dog is represented by an Zendesk user object. You can use the Search Users endpoint in the Support API to find users.
Note: In a future iteration, you plan on using the Profiles API to track pet owners. A profile consists of one or more identifiers for a person in one or more systems, as well as optional custom attributes about the person.
For legacy custom objects, a legacy relationship type consists of a key, a source, and a target. A key is the name you want to give to the relationship type. A source is the name of a legacy object type; a target is the name of the related object type. Either object type can be a legacy custom object type like "doggo" or a standard Zendesk object type like "zen:user" or "zen:ticket". See Zendesk object types in the API docs.
Example:
{
'key': 'owner_has_many_doggos',
'source': 'zen:user',
'target': ['doggo']
}
The square brackets around 'doggo' denote many. The example establishes a one-to-many relationship between a Zendesk user record and doggo records.
To create the legacy relationship type, make a POST request to the following endpoint:
POST /api/sunshine/relationships/types
For details, see Create Legacy Relationship Type in the API docs.
Try it yourself
-
Add the following
create_relationship_type()
function to your zendesk.py module:def create_relationship_type(key, source, target):
data = {'data': {'key': key, 'source': source, 'target': target}}
url = f'{zendesk}/api/sunshine/relationships/types'
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
response = requests.post(url, json=data, auth=credentials, headers=headers)
if response.status_code != 201:
print(f"\nFailed to create {key} relationship type with error {response.status_code}: {response.text}")
return False
return response.json()['data']
The
create_relationship_type()
function takes a key, a source object type, and a target object type, then makes a POST request to theapi/sunshine/relationships/types
endpoint. -
Create a file named create_owner_doggo_type.py in your project folder.
-
Paste the following code into the file:
import json
import zendesk
key = 'owner_has_many_doggos'
source = 'zen:user'
target = ['doggo']
response = zendesk.create_relationship_type(key, source, target)
if response:
print(f'\nSuccessfully created the {key} relationship type.\n')
print(json.dumps(response, indent=2, sort_keys=True) + '\n')
This one-off script supplies the data to define a relationship type between Zendesk users and doggo records. It imports the sunshine module you created and runs the module's
create_relationship_type()
function:response = zendesk.create_relationship_type(key, source, target)
-
Save the files.
-
In your command line interface, run the following command:
$ python3 create_owner_doggo_type.py
If successful, the console should display the new relationship type.
Note: You can't modify the relationship type once it's created. You'll need to delete it and create another one. See Relationship Types in the API docs.
Create the Doggos API
After defining a doggo legacy object type and a owner_has_many_doggos relationship type, you can start building the Doggos API. The API will have endpoints to create, read, or update doggo records. The DoggoGo dev teams will use the API to power new features for the company's web and mobile applications, as well as for the agent interface in Zendesk Support.
For the sake of simplicity, the API in this tutorial is built using Bottle, a minimalist Python web framework. To learn more about how Bottle works, see the Bottle docs. The framework includes a local web server that you can use for testing.
You'll start by setting up the app, and then add the API endpoints to it in the sections that follow.
Try it yourself
-
Create a file named doggo_api.py in your project folder.
-
Paste the following code into the file:
import json
import zendesk
from bottle import route, run, request, response
@route('/test')
def test():
return '<p>Hello World!</p>'
run(debug=True)
The route defines a
/test
url. -
Save the file.
-
In your command line interface, start the API application on Bottle's built-in local server:
$ python3 doggo_api.py
After starting, the Doggos API application runs at http://localhost:8080.
-
Paste the following url in your browser and press Enter.
http://localhost:8080/test
If successful, the browser will display a page with the line "Hello World!".
Build the Create Doggo endpoint
The Create Doggo endpoint should create a doggo record for a specified pet owner.
When you create a doggo record, you should also create a legacy relationship record between the new doggo record and the Zendesk user record of the pet owner. You'll use the relationship in the List Doggos endpoint to list dogs by pet owner.
The Create Doggo endpoint should have the following API path:
POST /api/doggos
The endpoint should accept a JSON object with the following properties:
Name | Type | Mandatory | Comment |
---|---|---|---|
pet_owner | integer | yes | Zendesk user id of the dog owner |
name | string | yes | Dog's name |
breed | string | no | Dog's breed |
birthday | string | no | Known or estimated birthday in the YYYY-MM-DD format |
conditions | string | no | Health issues, if any |
training | boolean | no | Whether the dog received obedience training |
temperament | string | no | Natural predisposition. Possible values: "calm", "confident", "energetic", "aggressive", "couch potato" |
strangers | string | no | Attitude with strangers. Possible values: "affectionate", "reserved", "fearful" |
Try it yourself
-
Paste the following route after the
/test
route in the doggo_api.py file:@route('/api/doggos', method='POST')
def add_doggo():
# create doggo record
attributes = request.json.get('doggo')
obj_type = 'doggo'
pet_owner = attributes.pop('pet_owner') # used in creating relationship
if 'name' not in attributes:
response.status = 400
return
doggo_record = zendesk.create_object_record(obj_type, attributes)
if 'error_code' in doggo_record:
response.status = doggo_record['error_code']
return
# create relationship record
rel_type = 'owner_has_many_doggos'
source = pet_owner
target = doggo_record['id']
relationship_record = zendesk.create_relationship_record(rel_type, source, target)
if 'error_code' in relationship_record:
response.status = relationship_record['error_code']
return
# send new doggo record in response
response.status = 201
response.headers['Content-Type'] = 'application/json'
return json.dumps({'record': doggo_record})
A lot of activity in this code. Let's take it one step at a time.
The route defines the API path of '/api/doggos' with an HTTP method of 'POST'.
When the API web app receives this request, it runs the
add_doggo()
function. The function starts by retrieving the data from the request body (attributes = request.json.get('doggo')
). It also specifies that the type of object you want to create with these attributes is 'doggo' (which you created in Define a doggo object type above).The function pops (or moves) the 'pet_owner' attribute from the attributes dictionary because the attribute is not supported in the doggo object schema. Instead, the value is used later in the function to create a relationship with the doggo record.
Next, the function passes the object type and attributes to the
create_object_record()
function in your zendesk module (defined in the next step). It uses the returned doggo record id to create a relationship between the pet owner (source) and the new doggo record (target) using thecreate_object_relationship()
function (defined in an upcoming step in this procedure). -
Add the following
create_object_record()
function to your zendesk.py module:def create_object_record(obj_type, attributes):
data = {'data': {'type': obj_type, 'attributes': attributes}}
url = f'{zendesk}/api/sunshine/objects/records'
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
response = requests.post(url, json=data, auth=credentials, headers=headers)
if response.status_code != 201:
return {'error_code': response.status_code}
return response.json()['data']
The
create_object_record()
function takes an object type and a dictionary that defines the attributes of the record to create. The function then makes a POST request to theapi/sunshine/objects/records
endpoint to create the object record. For details, see Create Legacy Object Record in the API docs. -
Add the following
create_relationship_record()
function to your zendesk.py module:def create_relationship_record(rel_type, source, target):
data = {'data': {'relationship_type': rel_type, 'source': f'zen:user:{source}', 'target': target}}
url = f'{zendesk}/api/sunshine/relationships/records'
headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
response = requests.post(url, json=data, auth=credentials, headers=headers)
if response.status_code != 201:
return {'error_code': response.status_code}
return response.json()['data']
The
create_relationship_record()
function takes a relationship type, a source record id, and a target record id. Because the source is a standard Zendesk user record, the source record id is modified to the correct format (f'zen:user:{source}'
). The function then makes a POST request to theapi/sunshine/relationships/records
endpoint to create the legacy relationship record. For details, see Create Legacy Relationship Record in the API docs. -
Save the files.
-
In your command line interface, start or restart the API on Bottle's built-in local server:
$ python3 doggo_api.py
If the local Bottle server is already running, make sure to shut it down first. You should always restart the local server when you make changes to doggo_api.py while the server is running. Press Control+C to stop the server.
-
Open a separate command-line window and run the following API request to create a doggo record:
curl http://localhost:8080/api/doggos \
-d '{"doggo": {"pet_owner": 123456, "name": "Gaston", "birthday": "2016-02-09", "conditions": "Fleas", "temperament": "energetic", "strangers": "reserved"}}' \
-H "Content-Type: application/json" \
-v -X POST
No need to navigate to the project folder -- it's an HTTP request to your local server.
Make sure to replace the "pet_owner" id with the id of an actual user in your Zendesk Support account. You can create a test user for this purpose.
If you prefer, you can use Postman to run the API request:
If successful, the Doggos API should return the doggo record that was created and stored in the Zendesk Sunshine infrastructure.
In Postman:
Build the List Doggos endpoint
The List Doggos endpoint should return an array of one or more doggo records for a specified pet owner. The pet owner is represented by a Zendesk user record.
The List Doggos endpoint should have the following API path:
GET /api/owners/{user_id}/doggos
The endpoint should specify a Zendesk user id.
Try it yourself
-
Add the following route to the doggo_api.py file.
@route('/api/owners/<user_id>/doggos')
def list_doggos(user_id):
user_id = f'zen:user:{user_id}'
rel_type = 'owner_has_many_doggos'
doggos = zendesk.list_related_object_records(user_id, rel_type)
if 'error_code' in doggos:
response.status = doggos['error_code']
return
response.headers['Content-Type'] = 'application/json'
return json.dumps({'doggos': doggos})
The route contains a
<user_id>
wildcard, making the route dynamic. The wildcard value is passed to the route'slist_doggos(user_id)
function. Because the user id refers to a Zendesk user, the function formats the id as (f'zen:user:{user_id}
).The function specifies that the API should use the 'owner_has_many_doggos' relationship type to get the doggo records.
Next, the function passes the user id and the relationship type to the
list_related_object_records()
function in your zendesk module (defined in the next step).Finally, the function returns the array of doggo records in the HTTP response.
-
Add the following
list_related_object_records()
function to your zendesk.py module:def list_related_object_records(record_id, rel_type):
url = f'{zendesk}/api/sunshine/objects/records/{record_id}/related/{rel_type}'
headers = {'Accept': 'application/json'}
response = requests.get(url, auth=credentials, headers=headers)
if response.status_code != 200:
return {'error_code': response.status_code}
return response.json()['data']
The
list_related_object_records()
function takes a record id and a legacy relationship type and passes it to the following Zendesk endpoint:GET /api/sunshine/objects/records/{id}/related/{relationship_type}
See List Legacy Related Object Records in the API docs for details.
The endpoint returns an array of related records.
-
Save the files.
-
In your command line interface, restart the API on the local server:
$ python3 doggo_api.py
If the local server is already running, make sure to shut it down first by pressing Control+C.
-
In a separate command-line window, run the following API request to list the pet owner's doggos:
curl http://localhost:8080/api/owners/123456/doggos -v
Replace the user id in the API path with the id of the Zendesk user you specified when you created the doggo record in the previous section.
If successful, the Doggos API should return an array containing the doggo record.
In Postman:
Build the Update Doggo endpoint
The Update Doggo endpoint should update a specified doggo record and return the record.
It should have the following API path:
PUT /api/doggos/{doggo_id}
The endpoint should specify a doggo record id and accept a JSON object with any of the following properties:
Name | Type | Comment |
---|---|---|
name | string | Dog's name |
breed | string | Dog's breed |
birthday | string | Known or estimated birthday in the YYYY-MM-DD format |
conditions | string | Health issues, if any |
training | boolean | Whether the dog received obedience training |
temperament | string | Natural predisposition. Possible values: "calm", "confident", "energetic", "aggressive", "couch potato" |
strangers | string | Attitude with strangers. Possible values: "affectionate", "reserved", "fearful" |
The pet_owner
property is not included because it's not part of the doggo schema.
Try it yourself
-
Add the following route to the doggo_api.py file.
@route('/api/doggos/<doggo_id>', method='PUT')
def update_doggo(doggo_id):
attributes = request.json.get('doggo')
record = zendesk.update_object_record(doggo_id, attributes)
if 'error_code' in record:
response.status = record['error_code']
return
response.status = 200
response.headers['Content-Type'] = 'application/json'
return json.dumps({'record': record})
The route uses a
<doggo_id>
wildcard to pass the value to theupdate_doggo(doggo_id)
function.The function retrieves the data for the update from the request body (
attributes = request.json.get('doggo')
). The function then passes the doggo id and the attribute data to theupdate_object_record()
function in your zendesk module (defined in the next step).Finally if all goes well, the function returns the updated doggo record in the HTTP response.
-
Add the following
update_object_record()
function to your zendesk.py module:def update_object_record(record_id, attributes):
url = f'{zendesk}/api/sunshine/objects/records/{record_id}'
data = {'data': {'attributes': attributes}}
headers = {'Content-Type': 'application/merge-patch+json', 'Accept': 'application/json'}
response = requests.patch(url, json=data, auth=credentials, headers=headers)
if response.status_code != 200:
return {'error_code': response.status_code}
return response.json()['data']
The
update_object_record()
function takes a record id and the attributes to update and passes it to the following Zendesk endpoint:PATCH /api/sunshine/objects/records/{record_id}
The request is a PATCH request. You must include a
'Content-Type': 'application/merge-patch+json'
header. See Update Legacy Object Record in the API docs for details. -
Save the files.
-
In your command line interface, restart the API on the local server:
$ python3 doggo_api.py
If the local server is already running, make sure to shut it down first by pressing Control+C.
-
In a separate command-line window, run the following API request to update a doggo record:
curl http://localhost:8080/api/doggos/4f1f0cd4-5653-11e9-90b0-37a64660d256
-d '{"doggo": {"birthday": "2016-03-19", "conditions": "Hip dysplasia"}}' \
-H "Content-Type: application/json" \
-v -X PUT
Replace the doggo id in the API path with the id of the doggo returned by the List Doggos endpoint in the last section.
If successful, the Doggos API should return the updated doggo record.
curl:
Postman:
Write the Doggos API documentation
The Doggos API needs to be documented before DoggoGo developers can start using it. A possible version of the docs is available in the DoggoGo API repo on GitHub.
Next steps
The obvious next step would be to build a DELETE endpoint for the Doggo API. However, DoggoGo doesn't see a need for it because it wants to keep the doggo records associated with each pet owner. If a dog passes away, the company wants to send a condolence message like "He was a good boy" or "She was a good girl" to the dog owner on learning of the passing and on the first anniversary. The information would also be useful to dog walkers to build customer relationships with owners with more than one pet.
Rather than deleting doggo records, the company's requirement means updating the schema of the doggo object with a field for the date of death and a field for the dog's sex (for the condolence message).
After further discussion, the DoggoGo company decides it also wants its dog walkers to rate the dogs on walks by entering a score of 1 to 5 in the mobile app after each walk. The average score for each dog will be useful to other dog walkers as well as to customer service agents. They want you to expand the Doggos API with a dog rating object with the following properties:
- Doggo id
- Walk rating
- Any other useful attributes about the walk, such as the time of day or the weather
To list and average the ratings for each dog, you'll also need a one-to-many relationship between dogs and ratings.
Enjoy building your own tailor-made APIs with Zendesk legacy custom objects.