Integration examples
Documentation
Integration examples
Section titled “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).
Any of the three also accepts several images of the same part as one search input, which is how the web interface lets a user add a second or third photo to a search. See Example 5 below and Search — Searching with multiple images.
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: Multi-view search with several images of the same part
Section titled “Example 5: Multi-view search with several images of the same part”Several images of the same part can be searched as one input. VizSeek treats them as different views of one subject, matches each view against a different view of every candidate, and returns a single ranked result list. Both routes below produce the same result.
def put_search_multi(token, local_paths, filter_parts=None): """Route 1: upload every image in one PUT /api/Search call (max 10 files).
All the files must share one extension, because fileExtension is a single query parameter applied to the whole request. """ extensions = {file_extension_for_api(path) for path in local_paths} if len(extensions) != 1: raise ValueError( "PUT /api/Search takes one fileExtension for all files; " "use search_by_file_uids() for mixed formats" )
query_parts = [f"fileExtension={urllib.parse.quote(extensions.pop(), 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_paths), timeout=300, ) response.raise_for_status() return response.json()
def search_by_file_uids(token, file_uids, filter_parts=None): """Route 2: search with search-input UIDs already uploaded via PUT /api/File.
The fileUID key inside filterStr takes a comma-separated list. Formats may differ between the inputs, and each upload can use indexAsync=true. """ parts = [f"fileUID={','.join(file_uids)}"] 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=300, ) response.raise_for_status() return response.json()
token = get_user_token()
# Route 1 - one call, one extension for all files.results = put_search_multi( token, [ "samples/bracket-front.jpg", "samples/bracket-side.jpg", "samples/bracket-top.jpg", ], filter_parts=["FT=document_type=[Assembly]"],)
# InputFileUID is a comma-separated list, one UID per uploaded image.input_uids = results["InputFileUID"].split(",")print(input_uids, results["TotalResults"])
# Re-run or refine the same multi-image search later, without re-uploading.refined = search_by_file_uids( token, input_uids, filter_parts=["FT=material=[Stainless Steel]"],)print(refined["TotalResults"])
# Route 2 from scratch - mixed formats are fine here.uids = [ put_search_input_file(token, "samples/bracket-front.jpg"), put_search_input_file(token, "samples/bracket-side.png"),]print(search_by_file_uids(token, uids)["TotalResults"])To add a photo to a search a user already ran, send only the new image to PUT /api/Search and pass the existing UIDs in filterStr — the endpoint appends the upload to the fileUID list rather than replacing it:
results = put_search_multi( token, ["samples/bracket-top.jpg"], filter_parts=[f"fileUID={','.join(input_uids)}"],)print(results["InputFileUID"]) # previous inputs plus the one just uploadedEvery image must show the same part and be the same kind of file (all images, all 2D, or all 3D). An unrelated image is not an OR — it is one more view each candidate has to match. See Search — Searching with multiple images for the full rules.
Example 6: Metadata-only search
Section titled “Example 6: 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 7: Get PMI data from a file
Section titled “Example 7: 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))