Skip to content
Main site Contact

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.

import base64
import hashlib
import json
import urllib.parse
from 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/Search for a synchronous search. You upload the input file and receive search results back in one API call.
  • Use PUT /api/File?isSearchInput=true for an asynchronous workflow. That returns a reusable fileUID, which you can inspect with GET /api/File, poll with GET /api/FileStatus if needed, and then pass into GET /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:

  1. Upload a temporary search-input file with PUT /api/File?isSearchInput=true.
  2. Reuse the returned fileUID in GET /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.

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].

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))