Written by @Mercylyne and @sergio.leon
Flow Production Tracking (FPTR) REST API offers a variety of functionalities, many like the Python API. To cover some of the aspects of the REST API, we are going to download a note thumbnail.
A note thumbnail refers to a small visual representation or preview image associated with a note. A note is an entity in FPTR.
When someone creates a note in FPTR, they have the option to include a thumbnail image to provide a visual context or reference related to the content of the note. This thumbnail can help others quickly understand the content or topic of the note without having to open the note and view the full details.
For example:
If a note discusses a lighting issue in a scene, the author or reviewer might attach a thumbnail showing the problematic frame. This helps artists instantly understand the context without opening the note itself, streamlining collaboration, and communication within the team.
How the REST API Works
The REST API uses standard HTTPS requests. You make REST calls to specific endpoints to query data or trigger changes executed on the server. The documentation lists the functionality exposed with the REST API on several sections. On it, we find the API reference together with explanations and samples.
All major programming languages have utilities and libraries to make a call to Restful APIs. In our case, we will use python. You can use here the ârequestsâ library. When you use the REST API from Python, these elements must be defined:
| Element | Structure | Description |
|---|---|---|
| URL (Endpoint) | https://{mysite_url}/api/v1.1/{functionality_endpoint} | Tells FPTR where to send the request |
| Headers | { âContent-Typeâ: âapplication/x-www-form-urlencodedâ, âAcceptâ: âapplication/jsonâ } | Tell FPTR how youâre sending and expecting data |
| Payload (Body) | { âclient_idâ: âmy_appâ, âclient_secretâ:âabcdef123456â, âgrant_typeâ:"client_credential} | Contains your credentials and other data |
| HTTP Method | POST (for sending data) or GET (for fetching) or PUT (for updates) or DELETE (for deleting) | Defines the action to perform. For authentication (access token), FPTR requires POST because credentials are sent in the request body. Other endpoints may use GET, PUT, or DELETE depending on whether you are fetching, updating, or removing data. |
In a REST API request, payloads are often sent as JSON or form data. In Python, theyâre usually represented as a dictionary. When sending the payload in a request, headers also tell FPTR what format youâre sending and expecting.
For development and testing, tools such as Postman and Insomnia are very useful. They allow you to build REST API requests, configure authentication and headers, send JSON or form-data payloads, and inspect responses. This helps validate request structure, verify headers like Content-Type and Accept, and debug API behavior before moving to production. The FPTR REST API has been streamlined for use with Postman and can be accessed using the setup instructions provided in the official documentation. https://developers.shotgridsoftware.com/rest-api/?python#setting-up-a-development-environment
Authentication
Getting Started with REST API Authentication
To begin leveraging REST API functionality, you must first authenticate using your FPTR credentials. The authentication process is essential to obtain an access token, which serves as a security key for accessing protected resources through the API.
Before you can interact with any endpoints that provide access to secured data, authentication is required. Specifically, you must include the access token in the Authorization header of your API payload to verify your identity and grant the necessary permissions for each request. Such as downloading a noteâs thumbnail image.
There are several accepted approaches to authentication, details of which can be found in the documentation:
The authentication endpoint expects three key-value pairs to be supplied in the payload:
-
client_id
-
client_secret
-
grant_type
payload = { âclient_idâ: "yourscriptnameâ,
âclient_secretâ: âyourscriptkeyâ,
âgrant_typeâ: âclient_credentialsâ # type of authentication
}
For the sample implementation in this guide, the access token is being generated using script credentials. It can also be done with a username and password. In that case the grant type would have been set to âpasswordâ as the documentation indicates.
The authentication endpoint URL is created by combining your FPTR site domain with the REST API authentication path that is fixed. The site domain identifies your specific FPTR instance, while /api/v1.1/ specifies the API version used, and /auth/access_token is the endpoint that accepts client credentials and returns an access token. It follows the OAuth2 standard protocol. The full authentication URL becomes
url = f"https://{MY_SITE_DOMAIN}/api/v1.1/auth/access_token", which is then used in a POST request to initiate authentication.
/api/v1.1/ specifies the latest version, v1 being the base version of the FPTR REST API. More information can be found in the link:
Endpoint for Entities and properties
The REST API documentation defines the fixed parts of every URL or endpoint: the base path (https://<site_domain>/api/v1.1), available resources (such as entity, schema, or auth), and the supported HTTP methods (GET, POST, PUT,DELETE). To build an endpoint, you first need to identify what action you want to perform (In our sample: authenticate, read an entity and download an image) and then follow the documented path patterns for these actions.
The FPTR Schema describes the entities, their relations and the structure of each entity type: which fields exist, their data types, and how entities relate to each other. When querying data, you use the schema to decide which entities and fields you want to request or filter on. For example, we know from the schema that Note is a valid entity type with an image-capable attachment. When adding parameters (such as filters, fields, or output variants), the schema ensures that the field names and relationships you reference actually exist, preventing invalid queries and guiding you to construct URLs and parameters that the API can successfully resolve.
For the entity note and the image endpoint, the URL starts with your FPTR site domain, which identifies the specific FPTR instance you are communicating with. This is followed by /api/v1.1, which specifies the REST API version (the latest version in this case).
If we need to list all the notes, and filter for one of them, you can query with a filter and parse the returning data. Please check these two resources on how to formulate the endpoint with a filter:
In our sample, for instance, we may want to search for the note based on the Author, the sequence, the shot and/or the name of the entity the note is linked to. For that we would use the following endpoint:
search_url = f"https://{MY_SITE_DOMAIN}/api/v1.1/entity/{entity}/_search
For the sample using this endpoint with filters, please check the sample at the end of this article.
Once we have found the entity (a note) and query the ID we can proceed. We may also know the ID from the web UI. We can list the IDs in the Note view as a column.
The endpoint would follow with /entity/{ENTITY_TYPE}/{ENTITY_ID} segment which tells FPTR exactly which entity you are targeting by combining the entity type (in this case a note) with its unique numeric ID. This path resolves to one concrete record in FPTR, such as a single Note.
As we can inspect in FPTR web site, when accessing the Notes, they have a field called Thumbnail. The internal code for the thumbnail is âimageâ, as we can see when we hover the mouse on the field column list.
Note that the thumbnail is a cloud file, a binary field of a Note. When handling files, we can request an entity file field according to this section:
Using the â/imageâ suffix we indicate that we are requesting the image field associated with that specific entity. Together with the use of the âaltâ query, we indicate to download the file instead of getting the field value. The query parameter â?alt=originalâ instructs FPTR to return the original uploaded image file instead of a resized thumbnail. If we want the resized thumbnail, we could use âalt=thumbnailâ. When this URL is accessed with a valid access token in the Authorization header, FPTR locates the stored image for that entity and streams the binary image data back in the response, and the image can hence get downloaded.
Consider also this link to âhow to interpret image fieldsâ,
The endpoint will at the end look like this:
url = f"https://{MY_SITE_DOMAIN}/api/v1.1/entity/note/{note_id}/image?alt=original"
Sample Code: downloading a noteâs thumbnail
Below is a working sample code of how you can download your note thumbnail using the REST API.
import requests
import shutil
# Some hardcoded variables here for demo test purposes
ENTITY_TYPE = ânoteâ
ENTITY_ID = 1234 #For demo purposes, this ID is hardcoded.
CLIENT_ID = <your_script_name>
CLIENT_SECRET = <your_script_secret> # Note, this should not be exposed in a script. Better to be place in an environment variable. For demo purposes, it is here.
MY_SITE_DOMAIN = <your_fptr_site_domain>
# Construct the URL for obtaining the access token
url = f"https://{MY_SITE_DOMAIN}/api/v1.1/auth/access_token"
# Construct the payload for authentication request
payload = {âclient_idâ: CLIENT_ID,
âclient_secretâ: CLIENT_SECRET,
âgrant_typeâ: âclient_credentialsâ}
# Define headers for the authentication request
headers = {
âContent-Typeâ: âapplication/x-www-form-urlencodedâ,
âAcceptâ: âapplication/jsonâ,
}
# Send a POST request to obtain the access token
response = requests.request(âPOSTâ, url, headers=headers, data=payload)
`# Error handling for the token request`
if response.status_code != 200:
print("Failed to obtain access token.")
print("Status code:", response.status_code)
print("Response:", response.text)
raise SystemExit()
json_data = response.json()
if âaccess_tokenâ not in json_data:
print("Access token not found in response.")
print("Response:", json_data)
raise SystemExit()
# Parse the access token from the response
access_token = json_data\[âaccess_tokenâ\]
# Construct the URL for downloading the image using the endpoint
url = f"https://{MY_SITE_DOMAIN}/api/v1.1/entity/{ENTITY_TYPE}/{ENTITY_ID}/image?alt=original"
# Build new GET Rest call
# Define headers for the image download request
headers = {
âAcceptâ: âapplication/jsonâ,
âAuthorizationâ: 'Bearer â + access_token,
}
# Send a GET request to download the image
res = requests.request(âGETâ, url, headers=headers, stream=True)
# Define the file name for the downloaded image
file_name = âthumbnail.jpgâ
# Check if the image was downloaded successfully
if res.status_code == 200:
# Save the image to a file
with open(file_name, 'wb+') as f:
shutil.copyfileobj(res.raw, f)
print('Image successfully downloaded:', file_name)
else:
print('Image couldn't be retrieved')
Save the code in a file download_note_thumbnail.py
Run the script using:
python download_note_thumbnail.py
If successful, youâll see the message confirming that the image was downloaded.
Sample Code: searching for a note
import requests
import json
MY_SITE_DOMAIN = "https://mysite.autodesk.com"
CLIENT_ID = 'my_script'
CLIENT_SECRET = 'my_password'
USER_NAME = ''
# Step 1: Get access token
token_url = f"{MY_SITE_DOMAIN}/api/v1.1/auth/access_token"
token_payload = {
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET, # Note, this should not be exposed in a script. Better to be place in an environment variable. For demo purposes, it is here.
'grant_type': 'client_credentials'
}
token_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
token_resp = requests.post(token_url, data=token_payload, headers=token_headers)
access_token = token_resp.json()['access_token']
# Step 2: Search for the User ID
user_entity = 'HumanUser'
user_search_url = f'{MY_SITE_DOMAIN}/api/v1.1/entity/{user_entity}/_search'
user_headers = {
'Content-Type': 'application/vnd+shotgun.api3_array+json',
'Accept': 'application/json',
'Authorization': f'Bearer {access_token}'
}
user_payload = {
"filters": [["name", "is", USER_NAME]],
"fields": ["id", "name"]
}
user_resp = requests.post(user_search_url, json=user_payload, headers=user_headers)
if user_resp.status_code == 200 and user_resp.json()['data']:
user_id = user_resp.json()['data'][0]['id']
print(f"Found User ID: {user_id}")
else:
print(f"User '{USER_NAME}' not found or error occurred: {user_resp.text}")
exit()
# Step 3: Search for notes
entity = 'Note'
search_url = f'{MY_SITE_DOMAIN}/api/v1.1/entity/{entity}/_search'
search_headers = {
'Content-Type': 'application/vnd+shotgun.api3_array+json',
'Accept': 'application/json',
'Authorization': f'Bearer {access_token}'
}
search_payload = {
"filters":
[["created_by", "is", {"type": "HumanUser", "id": user_id}]],
"fields": ["content", "created_at", "created_by", "id", "note_links"]
}
note_resp = requests.post(search_url, json=search_payload, headers=search_headers)
if note_resp.status_code == 200 and note_resp.json()['data']:
note_id = note_resp.json()['data'][0]['id']
print(f"Note ID: {note_id}")
else:
print(f"Note not found or error occurred: {note_resp.text}")
exit()
print("Note data:\n" + json.dumps(note_resp.json(), indent=2))
Save the code in a file search_note.py
Run the script using:
python search_note.py
Troubleshooting Common Issues
1. 400 Canât Authenticate User
-
Cause: Invalid credentials or expired token.
-
Fix: Recheck your client_id, client_secret, and site URL. Refresh the token if needed.
2. 403 Forbidden
-
Cause: Script lacks permission to access Notes.
-
Fix: Update the scriptâs permissions in FPTR admin settings.
3. 404 Not Found
-
Cause: Invalid entity type or ID.
-
Fix: Confirm that the Note exists.
4. Image Not Retrieved
-
Cause: Note may not have a thumbnail.
-
Fix: If ?alt=thumbnail does not return anything, check ?alt=original, or verify the Note actually contains an image.
5. Network or SSL Errors
- Fix: Ensure your system can reach your FPTR domain and uses HTTPS. Update certificates with:
python -m pip install certifi
Debugging Tip: Print response details for more context: print(response.text)
