Integration examples
These examples use neutral sample fields such as document_number, document_type, material, region, and weight(kg). They follow the current controller behavior, including the legacy JSON-string request bodies still required by PUT /api/File and PUT /api/Search.
Shared Python helpers
Section titled “Shared Python helpers”import base64import hashlibimport jsonimport urllib.parsefrom pathlib import Path
import requests
SERVER = "https://your-server.example.com"CLIENT_ID = "YOUR_CLIENT_ID"USERNAME = "user@example.com"PASSWORD = "YourPlainTextPassword"
def sha1_hex(text): return hashlib.sha1(text.encode("utf-8")).hexdigest()
def encode_filter_str(raw_filter): return urllib.parse.quote(raw_filter, safe="")
def encode_ft_expression(expression): encoded_expression = urllib.parse.quote(expression, safe="") return encode_filter_str(f"FT={encoded_expression}")
def file_extension_for_api(path): suffixes = Path(path).suffixes if not suffixes: raise ValueError(f"{path!r} has no file extension") return "".join(suffixes).lower()
def json_base64_body(path): return json.dumps(base64.b64encode(Path(path).read_bytes()).decode("ascii"))
def json_base64_array_body(*paths): encoded_files = [ base64.b64encode(Path(path).read_bytes()).decode("ascii") for path in paths ] return json.dumps(json.dumps(encoded_files))
def auth_headers(token, content_type="application/json"): return { "Accept": "application/json", "Authorization": f"Bearer {token}", "Content-Type": content_type, }
def get_user_token(): response = requests.post( f"{SERVER}/api/Token", headers={"Content-Type": "application/x-www-form-urlencoded"}, data={ "grant_type": "password", "client_id": CLIENT_ID, "username": USERNAME, "password": sha1_hex(PASSWORD), }, timeout=30, ) response.raise_for_status() return response.json()["access_token"]Choose the right file-based search workflow
Section titled “Choose the right file-based search workflow”- Use
PUT /api/Searchfor a synchronous search. You upload the input file and receive search results back in one API call. - Use
PUT /api/File?isSearchInput=truefor an asynchronous workflow. That returns a reusablefileUID, which you can inspect withGET /api/File, poll withGET /api/FileStatusif needed, and then pass intoGET /api/Search.
Example 1: Add a file to the company database with metadata
Section titled “Example 1: Add a file to the company database with metadata”The attributes query parameter is only applied when isSearchInput=false.
def put_file_to_database(token, local_path, stored_path, metadata): raw_attributes = "&".join(f"{name}={value}" for name, value in metadata.items()) response = requests.put( f"{SERVER}/api/File" f"?file={urllib.parse.quote(stored_path, safe='/')}" f"&isSearchInput=false" f"&attributes={urllib.parse.quote(raw_attributes, safe='')}", headers=auth_headers(token), data=json_base64_body(local_path), timeout=120, ) response.raise_for_status() return response.text.strip()
token = get_user_token()file_uid = put_file_to_database( token, local_path="samples/example-assembly.step", stored_path="Design/example-assembly.step", metadata={ "document_number": "AX-1000", "document_type": "Assembly", "material": "Stainless Steel", "revision": "4", "weight(kg)": "12.7", },)print(file_uid)Example 2: Check file data with GET /api/File
Section titled “Example 2: Check file data with GET /api/File”def get_file(token, file_uid, include_shapelets=False): params = {"fileUID": file_uid} if include_shapelets: params["includeShapelets"] = "true"
response = requests.get( f"{SERVER}/api/File", headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", }, params=params, timeout=30, ) response.raise_for_status() return response.json()
token = get_user_token()file_record = get_file(token, file_uid)print( json.dumps( { "UID": file_record["UID"], "FileName": file_record["FileName"], "Attributes": file_record.get("Attributes", []), "IndexingFinished": file_record.get("IndexingFinished"), }, indent=2, ))Example 3: Run a visual search in one call with PUT /api/Search
Section titled “Example 3: Run a visual search in one call with PUT /api/Search”This is the synchronous workflow. You can combine the uploaded search input with free text, metadata filters, or both.
def put_search(token, local_path, filter_parts=None): query_parts = [ f"fileExtension={urllib.parse.quote(file_extension_for_api(local_path), safe='')}" ] if filter_parts: query_parts.append( f"filterStr={encode_filter_str('&'.join(filter_parts))}" )
response = requests.put( f"{SERVER}/api/Search?{'&'.join(query_parts)}", headers=auth_headers(token), data=json_base64_array_body(local_path), timeout=120, ) response.raise_for_status() return response.json()
token = get_user_token()results = put_search( token, "samples/search-input.png", filter_parts=[ "FT=pump housing", "FT=document_type=[Assembly]", ],)print(json.dumps(results, indent=2))Example 4: Find similar files by fileUID with GET /api/Search
Section titled “Example 4: Find similar files by fileUID with GET /api/Search”This is the reusable two-step flow:
- Upload a temporary search-input file with
PUT /api/File?isSearchInput=true. - Reuse the returned
fileUIDinGET /api/Search.
def put_search_input_file(token, local_path): response = requests.put( f"{SERVER}/api/File" f"?file={urllib.parse.quote(file_extension_for_api(local_path), safe='')}" f"&isSearchInput=true", headers=auth_headers(token), data=json_base64_body(local_path), timeout=120, ) response.raise_for_status() return response.text.strip()
def search_by_file_uid(token, file_uid, filter_parts=None): parts = [f"fileUID={file_uid}"] if filter_parts: parts.extend(filter_parts)
response = requests.get( f"{SERVER}/api/Search?filterStr={encode_filter_str('&'.join(parts))}", headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", }, timeout=60, ) response.raise_for_status() return response.json()
token = get_user_token()input_file_uid = put_search_input_file(token, "samples/search-input.png")input_record = get_file(token, input_file_uid)similar_results = search_by_file_uid( token, input_file_uid, filter_parts=[ "FT=pump housing", "FT=document_type=[Assembly]", ],)
print(input_record["UID"])print(json.dumps(similar_results, indent=2))If you need to wait until indexing finishes before searching, poll GET /api/FileStatus with the returned fileUID.
Example 5: Metadata-only search
Section titled “Example 5: Metadata-only search”For metadata filters inside FT=:
- Regular search:
document_number=AX-1000 - Exact match:
document_number=[AX-1000] - Wildcard search:
document_number=AX-*
def search_by_metadata_expression(token, expression): response = requests.get( f"{SERVER}/api/Search?filterStr={encode_ft_expression(expression)}", headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", }, timeout=60, ) response.raise_for_status() return response.json()
token = get_user_token()regular_results = search_by_metadata_expression(token, "document_number=AX-1000")exact_results = search_by_metadata_expression(token, "document_number=[AX-1000]")wildcard_results = search_by_metadata_expression(token, "document_number=AX-*")
print(len(regular_results.get("ResultsList", [])))print(len(exact_results.get("ResultsList", [])))print(len(wildcard_results.get("ResultsList", [])))To add more metadata filters in the same request, repeat FT= inside the nested filterStr, for example FT=document_type=[Assembly]&FT=material=[Stainless Steel].
Example 6: Get PMI data from a file
Section titled “Example 6: Get PMI data from a file”The PMI field in the response is itself a JSON string, so parse it a second time if you want structured PMI objects.
def get_pmi_data(token, file_uid): response = requests.get( f"{SERVER}/api/PMIData", headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", }, params={"fileUID": file_uid}, timeout=60, ) response.raise_for_status() data = response.json() data["PMI"] = json.loads(data["PMI"]) if data.get("PMI") else [] return data
token = get_user_token()pmi_data = get_pmi_data(token, file_uid)print(json.dumps(pmi_data["ExtractedAttributes"], indent=2))print(json.dumps(pmi_data["PMI"], indent=2))