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 = "[email protected]"
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 a fileUID that is fully indexed and immediately searchable, then reused across one or more GET /api/Search calls. Best when the same input is searched more than once (see Example 4).
  • Two step, asynchronous — PUT /api/File?isSearchInput=true&indexAsync=true. Returns the fileUID immediately, before indexing completes, so large uploads do not risk a client or proxy timeout. Poll GET /api/FileStatus until it returns HTTP 200, then search by the fileUID (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:

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

Because indexAsync defaults to false, PUT /api/File?isSearchInput=true completes indexing before it returns, so the returned fileUID is ready to search immediately.

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

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