This document provides examples for managing user-defined lists using the ThreatSTOP API.
Overview
API requests
- A ThreatSTOP REST API token with Read/Write permissions on User Lists objects is required
- Tokens are generated using the ThreatSTOP Admin Portal User interface (API Keys page)
- The API token is tied to an account, and can manage the lists from the account it is associated with. To manage lists in multiple accounts, a token from each account must be used.
- API Endpoint: https://rest.threatstop.com/v4.0/
- Authentication is performed using Bearer tokens, provided as an HTTP header
Authorization: Bearer <Token> - Authentication error codes
- The API returns HTTP/401 if the token is missing
- The API returns HTTP/403 if the token is invalid or doesn’t have access to the requested resource
- Examples are provided using curl (https://curl.haxx.se/) and jq (https://stedolan.github.io/jq/manual/v1.4/) on a Unix system
Object types
- There are two types of user defined lists - one type for IP addresses and one for Domains. They are managed independently.
- Domain lists can only be used in DNS Defense (RPZ) policies, while IP lists can be used in both IP Defense and DNS Defense policies.
- The typical workflow consists of:
- Creating the user list, a one time operation, and retrieving its object ID
- Assigning it to a policy (not covered in this example, can be done via API or Portal)
- Adding/removing entries from the list. The changes can be incremental (adding/removing entries) or a full replacement of the content.
- User-defined Lists are restricted to a maximum of 32,000 records each. Multiple user-defined lists can be attached to a policy.
Data format
- Object IDs follow the UUID Format
- List names within an account must be unique
- List Names must be an alphanumerical string between 1 and 8 characters long
- IP addresses include a netmask (a.b.c.d/n)
- Expiration dates are written as DD/MM/YYYY
- Responses contain a HATEOAS subdocument as
_links
IP Lists
- Endpoint:
https://rest.threatstop.com/v4.0/user_ip_lists/ - To protect against mistakes, IP User-Defined lists can’t include a private (RFC1918) IP or subnets, and subnets larger than /12 cannot be added.
Creating a new IP list
- Request body
cat > create_ip_udl.json << EOF { "addresses": [ { "comments": "created via API", "value": "1.2.3.4" }, { "comments": "will expire soon", "expires": "03/29/2018", "value": "4.3.2.1/32" } ], "list_name": "api1test", "list_type": "block", "shared": false } EOF- description is an optional field (string up to 1024 characters)
- The expires field is an optional expiration date for the entries in the list.
- Each record can override the expiration date
- “shared” is not documented here (sharing of lists across multiple accounts). Parameter is optional, or must be set to False.
- Execute the request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists' -X POST --header "Content-Type: application/json" --data @create_ip_udl.json - Response
- HTTP/201 (CREATED) on success.
- HTTP/400 if the request could not be processed
- HTTP/500 for REST server errors
- The Object ID of the newly created user list is accessible as
._data[0].object_id.
- Sample
{ "_data": [ { "_links": { "self": { "href": "https://rest.threatstop.com/v4.0/user_ip_lists/51e8c042-6f0a-4186-8f7c-a8441e2909f3" } }, "addresses": [ { "address_type": "ip", "comments": "created via API", "expires": null, "value": "1.2.3.4" }, { "address_type": "netmask", "comments": "will expire soon", "expires": "2018-03-29", "value": "4.3.2.1/32" } ], "description": "", "list_name": "api1test", "list_type": "block", "object_id": "51e8c042-6f0a-4186-8f7c-a8441e2909f3", "shared": false } ], "_links": { "self": { "href": "https://rest.threatstop.com/v4.0/user_ip_lists" } } } - With jq:
cat response.json | jq -r "._data[0].object_id" 51e8c042-6f0a-4186-8f7c-a8441e2909f3 - It is possible to create records during the list creation by adding addresses in the same format as described in the Add Records below. See the Python 3 example at the end of this document.
Retrieving the list of IP user defined lists
- You can retrieve the lists of User-defined lists with a GET request.
- The response will be an array with the user-defined lists available in the account (same format as returned by the creation request).
- You can retrieve the Object ID as
._data[entry_number].object_idcurl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists' -X GET - Response codes
- HTTP/200 on success
- Sample response
{ "_data": [ { "_links": { "self": { "href": "https://rest.threatstop.com/v4.0/user_ip_lists/368ecff4-a524-4b24-a261-77ed271fdf1c" } }, "_meta": { "addresses": { "address_count": 413, "record_count": 413 } }, "description": "", "list_name": "bruteforce", "list_type": "block", "object_id": "368ecff4-a524-4b24-a261-77ed271fdf1c", "shared": false } ], "_meta": { "count": 1 } } - The meta data section contains the number of user lists (as
._meta.count) - Each lists object also includes a metadata section with:
- the number of records in the list (
._data[0]._meta.addresses.record_count) - the number of IP addresses in the list (
._data[0]._meta.addresses.address_count), computed using the size of the subnets it contains
- the number of records in the list (
Managing records
Endpoint
https://rest.threatstop.com/v4.0/user_ip_lists/{ Object ID }
Retrieving records
- Records are retrieved with a GET request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists/368ecff4-a524-4b24-a261-77ed271fdf1c' -X GET
Note: Please note the use of the PATCH HTTP method in the examples below
Adding a record
- Sample request
cat > add_ip.json << EOF { "addresses": [ { "value": "1.2.3.4", "action": "add" } ] } EOF - Execute the request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists/8c4350db-4439-4458-9687-af7dc2c877e6' -X PATCH --header "Content-Type: application/json" --data @add_ip.json - Response codes
- 200 on success
- 400 if the request could not be processed
Deleting a record
- Sample request
cat > del_ip.json << EOF { "addresses": [ { "value": "1.2.3.4", "action": "remove" } ] } EOF - Execute the request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists/8c4350db-4439-4458-9687-af7dc2c877e6' -X PATCH --header "Content-Type: application/json" --data @del_ip.json - Response codes
- 200 on success
- 400 if the request could not be processed
Updating a record
- Sample request
cat > update_ip.json << EOF { "addresses": [ { "value": "1.2.3.4", "comments": "new comment" } ] } EOF - Execute the request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_ip_lists/8c4350db-4439-4458-9687-af7dc2c877e6' -X PATCH --header "Content-Type: application/json" --data @update_ip.json - Response codes
- 200 on success
- 400 if the request could not be processed
Domain Lists
- Endpoint:
https://rest.threatstop.com/v4.0/user_domain_lists/ - Requests and responses use the same format as IP list
Creating a new domain list
- Sample request
cat > create_domain_udl.json << EOF { "addresses": [ { "comments": "created via API", "value": "example.com" }, { "comments": "will expire soon", "expires": "03/29/2018", "value": "sub.example.com" } ], "list_name": "api2test", "shared": false } EOF- The expiration date is an optional expiration date for the entries in the list.
- Each record can override the expiration date
- “shared” is not documented here (sharing of lists across multiple accounts). Set to false
- Execute the request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_domain_lists' -X POST --header "Content-Type: application/json" --data @create_domain_udl.json - Response
- HTTP/201 (CREATED) on success.
- HTTP/400 if the request could not be processed
- HTTP/500 for REST server errors
- The Object ID of the newly created user list is accessible as
._data[0].object_id.
-
The response object has the same format as the IP list object
- Retrieving the object ID of the new list with jq:
cat response.json | jq -r "._data[0].object_id" 51e8c042-6f0a-4186-8f7c-a8441e2909f3 - It is possible to create records during the list creation by adding addresses in the same format as described in the Add Records below. See the Python 3 example at the end of this document.
Managing records
Endpoint
https://rest.threatstop.com/v4.0/user_domain_lists/{ Object ID }
Retrieving records
- Records are retrieved with a GET request
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_domain_lists/cd358077-5ecb-40a2-aec3-82b7ad880d8d' -X GET
Note: Please note the use of the PATCH HTTP method in the examples below
Adding a record
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_domain_lists/8612ab35-118c-4afd-9891-cf32b019c219' -X PATCH --header "Content-Type: application/json" --data '{"addresses": [{"value": "example.com", "action": "add"}]}'
Deleting a record
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_domain_lists/8612ab35-118c-4afd-9891-cf32b019c219' -X PATCH --header "Content-Type: application/json" --data '{"addresses": [{"value": "example.com", "action": "remove"}]}'
Updating a record
curl --silent --header 'Authorization: Bearer <API Token>' 'https://rest.threatstop.com/v4.0/user_domain_lists/8612ab35-118c-4afd-9891-cf32b019c219' -X PATCH --header "Content-Type: application/json" --data '{"addresses": [{"value": "example.com", "comments": "new comment"}]}'
Python 3 Example
Pre-requisites
- Create a virtual environment
virtualenv -p python3 env - Install the requests package
pip install requests
Sample code
#!/usr/bin/env python3
import sys
import requests
api_url = 'https://rest.threatstop.com/'
api_version = 'v4.0'
# put your API token here
api_key = 'YOUR_API_TOKEN'
ip_list_name = 'apitestip'
domain_list_name = 'apitestdom'
# Create a new user IP list with 2 records
response = requests.post(
url='{api_url}/{version}/{endpoint}'.format(
api_url=api_url,
version=api_version,
endpoint='user_ip_lists'
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"list_name": ip_list_name,
"list_type": "block",
"shared": False,
"addresses": [
{
"value": "1.2.3.4",
"comments": "created via API"
},
{
"value": "4.3.2.1/32",
"comments": "will expire soon",
"expires": "03/29/2018"
}
]
}
)
# Response handling
if response.status_code == 201:
data = response.json()['_data'][0]
print("User IP list {} created; Object ID {}".format(
data['list_name'], data['object_id']
))
else:
print("Unable to create user list: {}".format(
response.json()
))
sys.exit(101)
# Create user domain list with 2 records
response = requests.post(
url='{api_url}/{version}/{endpoint}'.format(
api_url=api_url,
version=api_version,
endpoint='user_domain_lists'
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"list_name": domain_list_name,
"shared": False,
"addresses": [
{
"value": "example.com",
"comments": "created via API"
},
{
"value": "sub.example.com",
"comments": "will expire soon",
"expires": "03/29/2018"
}
]
}
)
# Response handling
if response.status_code == 201:
data = response.json()['_data'][0]
print("User domain list {} created; Object ID {}".format(
data['list_name'], data['object_id']
))
else:
print("Unable to create user list: {}".format(
response.json()
))
sys.exit(101)
# Find Object ID of a newly-created IP list.
# It can also be found in the POST response
response = requests.get(
url='{api_url}/{version}/{endpoint}'.format(
api_url=api_url,
version=api_version,
endpoint='user_ip_lists'
),
headers={'Authorization': 'Bearer {}'.format(api_key)}
)
if response.status_code == 200:
for record in response.json()['_data']:
if record['list_name'] == ip_list_name:
ip_list_object_id = record['object_id']
print('Found user ip list {} with name {}'.format(
record['object_id'], record['list_name']
))
break
else:
print('User list {} not found.'.format(ip_list_name))
sys.exit(102)
else:
print('Unable to get list of the user lists: {}'.format(
response.json()
))
sys.exit(102)
# Find Object ID of a newly-created domain list.
# It can also be found in POST response
response = requests.get(
url='{api_url}/{version}/{endpoint}'.format(
api_url=api_url,
version=api_version,
endpoint='user_domain_lists'
),
headers={'Authorization': 'Bearer {}'.format(api_key)}
)
if response.status_code == 200:
for record in response.json()['_data']:
if record['list_name'] == domain_list_name:
domain_list_object_id = record['object_id']
print('Found user domain list {} with name {}'.format(
record['object_id'], record['list_name']
))
break
else:
print('User list {} not found.'.format(domain_list_name))
sys.exit(102)
else:
print('Unable to get list of the user lists: {}'.format(response.json()))
sys.exit(102)
# Delete one IP record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_ip_lists',
object_id=ip_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "1.2.3.4", "action": "remove"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(
response.json()['_meta']['addresses']
))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
# Add one IP record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_ip_lists',
object_id=ip_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "1.2.3.4", "action": "add"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(
response.json()['_meta']['addresses']
))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
# Add IP record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_ip_lists',
object_id=ip_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "1.2.3.4", "comments": "new comment"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(
response.json()['_meta']['addresses']
))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
# Delete domain record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_domain_lists',
object_id=domain_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "example.com", "action": "remove"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(
response.json()['_meta']['addresses']
))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
# Add domain record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_domain_lists',
object_id=domain_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "example.com", "action": "add"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(
response.json()['_meta']['addresses']
))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
# Update domain record
response = requests.patch(
url='{api_url}/{version}/{endpoint}/{object_id}'.format(
api_url=api_url,
version=api_version,
endpoint='user_domain_lists',
object_id=domain_list_object_id
),
headers={'Authorization': 'Bearer {}'.format(api_key)},
json={
"addresses": [
{"value": "example.com", "comments": "new comment"},
]
}
)
if response.status_code == 200:
print('User IP list updated: {}'.format(response.json()['_meta']['addresses']))
else:
print('Unable to patch user IP list: '.format(response.json()))
sys.exit(103)
Extended error codes
If the API returns an HTTP error (typically, status code 400), it will include a JSON object with details about the error.
- Error format
{ "additional_info": { "detail": "<error details>", "error_code": <extended_error_code> }, "error_description": "<message>", "status_code": 400 }
| Code | Meaning |
|---|---|
| 10301 | POST request with an Object ID |
| 11000 | No auth token provided |
| 11001 | Invalid auth token |
| 11002 | Expired Auth token |
| 11400 | Bad parameters in request |
| 19000 | User List name already in use |
| 19011 | Exceed maximum number of addresses |
| 19013 | Domains exceeded the maximum length (200 char) |
| 19012 | Request included forbidden network entry |
| 19050 | Invalid address type in request |