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"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”VizSeek supports three file-based search workflows. Select one based on the requirement for results in a single call, a reusable indexed input, or a non-blocking upload for large files:
- Single call —
PUT /api/Search. Upload the search input and receive results in the same response. Best for one-off searches. - Two step, synchronous —
PUT /api/File?isSearchInput=true. Returns afileUIDthat is fully indexed and immediately searchable, then reused across one or moreGET /api/Searchcalls. Best when the same input is searched more than once (see Example 4). - Two step, asynchronous —
PUT /api/File?isSearchInput=true&indexAsync=true. Returns thefileUIDimmediately, before indexing completes, so large uploads do not risk a client or proxy timeout. PollGET /api/FileStatusuntil it returns HTTP200, then search by thefileUID(see the asynchronous variant in Example 4).
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. The uploaded search input can be combined 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))Because indexAsync defaults to false, PUT /api/File?isSearchInput=true completes indexing before it returns, so the returned fileUID is ready to search immediately.
Asynchronous variant for large inputs
Section titled “Asynchronous variant for large inputs”Large search inputs can take long enough to index that a synchronous upload risks a client or proxy timeout. Add indexAsync=true to return the fileUID immediately, then poll GET /api/FileStatus until indexing completes before searching. GET /api/FileStatus signals state through the HTTP status code: 204 while indexing is in progress, 200 with a timestamp once it is complete, and 422 if indexing failed (see Files — GET /api/FileStatus for the full contract).
import time
def put_search_input_file_async(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" f"&indexAsync=true", headers=auth_headers(token), data=json_base64_body(local_path), timeout=120, ) response.raise_for_status() return response.text.strip()
def wait_until_indexed(token, file_uid, timeout_seconds=600, poll_interval=3): deadline = time.monotonic() + timeout_seconds while time.monotonic() < deadline: response = requests.get( f"{SERVER}/api/FileStatus", headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, params={"fileUID": file_uid}, timeout=30, ) if response.status_code == 200: # indexing complete; value is the index timestamp return response.text.strip().strip('"') if response.status_code == 204: # indexing still in progress time.sleep(poll_interval) continue if response.status_code == 422: raise RuntimeError(f"Indexing failed for {file_uid}; re-upload the file") if response.status_code == 404: raise LookupError(f"Unknown fileUID {file_uid}; was it uploaded (or deleted)?") response.raise_for_status() # 400 / 401 / unexpected statuses raise TimeoutError(f"Indexing did not complete within {timeout_seconds}s for {file_uid}")
token = get_user_token()input_file_uid = put_search_input_file_async(token, "samples/large-search-input.step")wait_until_indexed(token, input_file_uid)similar_results = search_by_file_uid(token, input_file_uid)print(json.dumps(similar_results, indent=2))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))