Skip to content
Main site Contact

Search

Documentation

This page covers the public search workflow:

  • GET /api/Search
  • PUT /api/Search
  • GET /api/Image
  • PUT /api/SearchFeedback

filterStr is not a normal field. It is its own mini query string, sent as the value of a query parameter. That means:

  1. Build the inner filter string first, for example fileUID=<guid> or FT=pump housing.
  2. URL-encode the entire inner string before appending it as filterStr=....
  3. If the FT= value itself contains = or &, encode the FT value first, then encode the whole filterStr.
  • Decoded inner filter string: FT=pump housing
  • Encoded filterStr: FT%3Dpump%20housing

If the logical search text is document_type=Assembly&material=Stainless Steel:

  1. Encode the FT value: document_type%3DAssembly%26material%3DStainless%20Steel
  2. Build the decoded filterStr: FT=document_type%3DAssembly%26material%3DStainless%20Steel
  3. Encode the whole filterStr: FT%3Ddocument_type%253DAssembly%2526material%253DStainless%2520Steel

Example: repeated FT= filters for metadata-style narrowing

Section titled “Example: repeated FT= filters for metadata-style narrowing”
  • Decoded inner filter string: FT=document_type=[Assembly]&FT=material=[Stainless Steel]&FT=region=[North America]
  • Encoded filterStr: FT%3Ddocument_type%3D%5BAssembly%5D%26FT%3Dmaterial%3D%5BStainless%20Steel%5D%26FT%3Dregion%3D%5BNorth%20America%5D

The current search parser treats the following forms differently:

  • Regular metadata search: FT=document_number=AX-1000
  • Exact attribute match: FT=document_number=[AX-1000]
  • Wildcard attribute search: FT=document_number=AX-*
  • Multi-valued attributes (values uploaded pipe-separated, e.g. part_id=REC-001|REC-002) match on any single value: FT=part_id=[REC-002] returns the file. See Multi-valued attributes.

Use this when the search input already exists in VizSeek, or when you want a pure text/attribute search.

ItemValue
MethodGET
Path/api/Search
AuthUser bearer token
ResponseSearchResultSummary JSON
ParameterRequiredFormatNotes
filterStrYesURL-encoded nested query stringSee the encoding rules above. Omitting filterStr does not return 400 — the search simply has no input. Supply at least one of fileUID or FT inside it for meaningful results.
includeShapeletsNotrue or falseAdds shapelet detail for applicable results.
customAPINoIntegerDeprecated legacy switch. Do not use for new integrations.
searchAssemblyComponentsNotrue or falseFor assembly input, return component-level matches instead of only whole-assembly matches.
accountNameNoStringOnly use if VizSeek explicitly tells you to use it.
rankAttributeNoString (attribute field name)De-duplicate results by this attribute — at most one result per unique value, e.g. category — and switch Score to a confidence-based value (confidence = 1 − Score). See De-duplicating results by an attribute and Confidence scores with rankAttribute.
Key inside filterStrMeaning
fileUIDSearch by an existing uploaded file UID. Pass a comma-separated list to search with several images of the same part at once — see Searching with multiple images.
FTFree-text or attribute-text search term. Repeat FT= when you want multiple text or metadata refinements.
resultFileTypeRestrict results to Image, ThreeD, TwoD, or Text.
vVolume tolerance for 3D-to-3D matching.
bbvBounding-box volume tolerance for 3D-to-3D matching.
saSurface-area tolerance for 3D-to-3D matching.
fileShapeletIndexSearch with specific extracted shape(s) or a page of the input file instead of the entire file. See Searching with a specific shape or page.
rankAttributeDe-duplicate results by this attribute — at most one result per unique value, e.g. category — and switch Score to a confidence-based value (confidence = 1 − Score). See De-duplicating results by an attribute and Confidence scores with rankAttribute.

Additional public refinement keys may appear in SearchFilters[].QueryString in the response. To refine a search, prefer reusing the QueryString values the API returns instead of composing custom filter names.

For v, bbv, and sa, the current public format is a decimal percentage string:

  • .1 means +/-10%
  • +.2 means +20%
  • -.5 means -50%
  • +0.3/-0.4 means +30% and -40%

Searching with a specific shape or page (fileShapeletIndex)

Section titled “Searching with a specific shape or page (fileShapeletIndex)”

For 2D drawing, image, and multi-page document inputs, VizSeek extracts individual shapes (and pages) at indexing time. By default a fileUID search uses the entire file; add fileShapeletIndex inside filterStr to search with one or more specific shapes instead — the same thing the web interface’s shape-selection popup does.

ValueMeaning
0Search with the first extracted shape (0-based index)
0,2Search with multiple shapes: comma-separated indexes
-1 (or key omitted)Search with the entire file
View:1Search with the second page as a whole (View:0 is the first page of a multi-page input)

The index is the position of the shape in the Shapelets array returned by GET /api/File with includeShapelets=true for the input file — the shapes appear in the same order in the web interface.

Typical workflow:

  1. Upload the input with PUT /api/File (isSearchInput=true).
  2. Call GET /api/File?fileUID=<uid>&includeShapelets=true and pick the shape from Shapelets.
  3. Search with the chosen index inside filterStr.
  • Decoded inner filter string: fileUID=11c20df1-8d84-418d-93ae-db3abb5d4d14&resultFileType=TwoD&fileShapeletIndex=0
  • Encoded request:
GET /api/Search?filterStr=fileUID%3D11c20df1-8d84-418d-93ae-db3abb5d4d14%26resultFileType%3DTwoD%26fileShapeletIndex%3D0 HTTP/1.1
Host: your-server.example.com
Authorization: Bearer USER_BEARER_TOKEN
import urllib.parse
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
file_uid = "11c20df1-8d84-418d-93ae-db3abb5d4d14"
filter_str = urllib.parse.quote(
f"fileUID={file_uid}&resultFileType=TwoD&fileShapeletIndex=0",
safe="",
)
resp = requests.get(
f"{server}/api/Search?filterStr={filter_str}",
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(resp.json())

Python example: search by existing file UID

Section titled “Python example: search by existing file UID”
import urllib.parse
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
file_uid = "11c20df1-8d84-418d-93ae-db3abb5d4d14"
filter_str = urllib.parse.quote(f"fileUID={file_uid}", safe="")
resp = requests.get(
f"{server}/api/Search?filterStr={filter_str}",
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(resp.json())
import urllib.parse
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
inner_ft = urllib.parse.quote("document_number=[AX-1000]", safe="")
filter_str = urllib.parse.quote(f"FT={inner_ft}", safe="")
resp = requests.get(
f"{server}/api/Search?filterStr={filter_str}",
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(resp.json())
GET /api/Search?filterStr=FT%3Dpump%20housing HTTP/1.1
Host: your-server.example.com
Authorization: Bearer USER_BEARER_TOKEN

Use this when you want to upload one or more temporary search-input files and search immediately. Uploading several images of the same part in one call is a multi-view search — see Searching with multiple images.

ItemValue
MethodPUT
Path/api/Search
AuthUser bearer token
Content-Typeapplication/json
ResponseSearchResultSummary JSON
ParameterRequiredFormatNotes
fileExtensionYesURL-encoded extension such as .png or .stpInclude the leading dot. One value applies to every file in the body, so a multi-file request must upload files that share an extension. To combine .jpg and .png inputs, use the two-step route in Searching with multiple images.
filterStrNoURL-encoded nested query stringSame rules as GET /api/Search.
isGZipCompressedNotrue or falseLegacy. Only use if the uploaded bytes are actually gzipped.
cropX, cropY, cropW, cropHNoIntegerDeprecated crop parameters.
includeShapeletsNotrue or falseInclude shapelet detail in response.
customAPINoIntegerDeprecated.
crawl_parameterNoStringDeprecated.
accountNameNoStringOnly use if VizSeek directs you to.
targetFidsNoComma-delimited file UID listRestrict search targets to those files.
rankAttributeNoString (attribute field name)De-duplicate results by this attribute — at most one result per unique value, e.g. category — and switch Score to a confidence-based value (confidence = 1 − Score). See De-duplicating results by an attribute and Confidence scores with rankAttribute.

The request body is a JSON string whose contents are a JSON array of base64-encoded file byte arrays.

That means the wire body looks like this:

"[\"BASE64_FILE_1\",\"BASE64_FILE_2\"]"

Do not send a raw JSON array like ["BASE64_FILE_1"]. The controller receives a string and then deserializes that string into byte[][].

Other important limits and quirks:

  • Maximum 10 input files per request. An 11th file returns 400 "Can not process more than 10 input files".
  • Every entry in the array becomes a separate search input, and they are searched together as multiple views of one part rather than as separate searches. See Searching with multiple images.
  • The endpoint rejects obviously invalid or very short payloads before deserialization.
  • fileExtension applies to every uploaded file in this request.

Python example: visual search from one file

Section titled “Python example: visual search from one file”
import base64
import json
import requests
import urllib.parse
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
filename = "example-input.dxf"
with open(filename, "rb") as f:
b64_file = base64.b64encode(f.read()).decode("ascii")
body = json.dumps(json.dumps([b64_file]))
filter_str = urllib.parse.quote(
"FT=pump housing&FT=document_type=[Assembly]",
safe="",
)
resp = requests.put(
f"{server}/api/Search?fileExtension=.dxf&filterStr={filter_str}",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
data=body,
)
resp.raise_for_status()
print(resp.json())

Python example: metadata narrowing with repeated FT= clauses

Section titled “Python example: metadata narrowing with repeated FT= clauses”
metadata_filter = (
"FT=document_type=[Assembly]"
"&FT=material=[Stainless Steel]"
"&FT=region=[North America]"
)
encoded_filter = urllib.parse.quote(metadata_filter, safe="")
PUT /api/Search?fileExtension=.stp&filterStr=FT%3Dpump%20housing HTTP/1.1
Host: your-server.example.com
Authorization: Bearer USER_BEARER_TOKEN
Content-Type: application/json
"[\"BASE64_FILE_1\"]"

VizSeek can take more than one image of the same part as a single search input. This is the same thing the web interface does when a user adds a second or third picture to a search, and it is fully supported by the API.

The engine treats the images as different views of one subject. Each input view is matched against a different view of every candidate file, and those per-view matches are averaged into one score per result. The response is a single ranked result list, not one list per image.

Use it when one photo is ambiguous: a front and a side view of a bracket, a photo plus a drawing, several angles of a moulded part. Results usually tighten, because a candidate now has to look right from every angle supplied.

All inputs must also be the same kind of file: all images, all 2D drawings, or all 3D models. Do not mix an image with a 3D model in one search — the search input type is taken from the first input.

Three routes produce the same result. Pick the one that fits what you already have.

Route 1: upload every image in one call (PUT /api/Search)

Section titled “Route 1: upload every image in one call (PUT /api/Search)”

Put one base64 entry per image in the body array. This is the shortest path, and the right choice for a straightforward “the user picked N photos, run the search” flow.

"[\"BASE64_IMAGE_1\",\"BASE64_IMAGE_2\",\"BASE64_IMAGE_3\"]"

fileExtension is a single query parameter applied to every file in the body, so all the images in one call must share an extension.

import base64
import json
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
images = ["bracket-front.jpg", "bracket-side.jpg", "bracket-top.jpg"]
def b64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("ascii")
# Note the double json.dumps: a JSON string whose contents are a JSON array.
body = json.dumps(json.dumps([b64(path) for path in images]))
resp = requests.put(
f"{server}/api/Search?fileExtension=.jpg",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
data=body,
timeout=300,
)
resp.raise_for_status()
results = resp.json()
# "6b6b9c3c-...,97c1c661-...,c2f93a31-..." - one UID per uploaded image, in upload order.
print(results["InputFileUID"])
print(results["TotalResults"])

InputFileUID comes back as a comma-separated list of the input UIDs the call created, in upload order. Keep it: it is exactly the value to send back as fileUID to re-run or refine the same multi-image search with GET /api/Search.

Route 2: upload each image separately, then search (PUT /api/File + GET /api/Search)

Section titled “Route 2: upload each image separately, then search (PUT /api/File + GET /api/Search)”

Upload each image on its own with isSearchInput=true, collect the returned UIDs, and join them with commas in the fileUID key inside filterStr.

This route lifts both limits of route 1: the image formats may differ (.jpg alongside .png), and each upload is its own request, so a large input can use indexAsync=true and be polled with GET /api/FileStatus instead of risking a proxy timeout. It is also the right route when the same inputs are reused across several searches.

import base64
import json
import urllib.parse
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
images = ["bracket-front.jpg", "bracket-side.png"]
def b64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("ascii")
# 1. Upload each view. Single json.dumps here - PUT /api/File takes one file, not an array.
uids = []
for path in images:
resp = requests.put(
f"{server}/api/File"
f"?file={urllib.parse.quote(path, safe='')}&isSearchInput=true",
headers=headers,
data=json.dumps(b64(path)),
timeout=300,
)
resp.raise_for_status()
uids.append(resp.text.strip().strip('"'))
# 2. Search with all of them: fileUID=<uid1>,<uid2> inside filterStr.
filter_str = urllib.parse.quote(f"fileUID={','.join(uids)}", safe="")
resp = requests.get(
f"{server}/api/Search?filterStr={filter_str}",
headers=headers,
timeout=300,
)
resp.raise_for_status()
print(resp.json()["TotalResults"])

The decoded inner filter string is:

fileUID=f64d1c7c-2bfc-463f-84fb-0ac1000d71d5,5a40c9eb-d0c0-40a9-9a38-db835c6befec

Comma-separated, no spaces. Other filterStr keys such as FT= and resultFileType= combine with it as usual.

GET /api/Search?filterStr=fileUID%3Df64d1c7c-2bfc-463f-84fb-0ac1000d71d5%2C5a40c9eb-d0c0-40a9-9a38-db835c6befec HTTP/1.1
Host: your-server.example.com
Authorization: Bearer USER_BEARER_TOKEN

Route 3: add an image to a search that already ran

Section titled “Route 3: add an image to a search that already ran”

To let a user refine a search by adding another photo, send only the new image to PUT /api/Search and pass the UIDs already in play through filterStr. The endpoint appends the newly uploaded file to the existing fileUID list instead of replacing it.

existing_uids = "f64d1c7c-2bfc-463f-84fb-0ac1000d71d5" # from the previous response's InputFileUID
filter_str = urllib.parse.quote(f"fileUID={existing_uids}", safe="")
body = json.dumps(json.dumps([b64("bracket-top.jpg")]))
resp = requests.put(
f"{server}/api/Search?fileExtension=.jpg&filterStr={filter_str}",
headers=headers,
data=body,
timeout=300,
)
resp.raise_for_status()
# "f64d1c7c-...,c2f93a31-..." - the previous input plus the one just uploaded.
print(resp.json()["InputFileUID"])

The same works in reverse: to drop one image from the search, re-issue GET /api/Search with that UID removed from the comma-separated fileUID list.

RuleDetail
Images per requestPUT /api/Search accepts at most 10 files in one body; an 11th returns 400. The fileUID list on GET /api/Search is not capped by the API, but stay at or below the same practical limit — every extra view adds matching work against every candidate.
Same partAll inputs must be views of the same part.
Same file kindAll images, all 2D, or all 3D. The search input type is taken from the first input.
Same extension (route 1 only)fileExtension is one value for the whole call. Mixed formats need route 2.
OrderDoes not affect the match. InputFileUID echoes the order the inputs were added.
Response shapeUnchanged from a single-image search, except that InputFileUID is a comma-separated list, and the SearchFilters entry whose QueryString is fileUID carries the same list in its Value.
TimingRoute 1 indexes every uploaded image before searching, inside the one request. For several large inputs prefer route 2 with indexAsync=true, so no single request has to carry all of that work.

By default a single search can return many results that share the same value of an attribute. Pass the optional rankAttribute parameter to collapse those: the search then returns at most one result per unique value of that attribute — one representative per distinct value — so the result set spans different attribute values instead of being dominated by one.

For example, with rankAttribute unset a 50-result response could be 50 files that all share the same category value; with rankAttribute=category the response instead contains up to 50 results, each with a different category value.

  • Pass a company attribute name — exactly as it appears in GET /api/FileAttributes. (category here is just a placeholder; use one of your own attribute names.) Matching is case-insensitive (category and Category are equivalent); the API resolves it to attributes.<name> for the backend. You may also pass the fully-qualified attributes.category.
  • Supply it either as a standalone query parameter (&rankAttribute=category) or as a key inside filterStr (alongside fileUID, FT, …). If both are present, the standalone query parameter wins.
  • It de-duplicates the result set; it does not otherwise filter results. Combine it with filterStr to narrow results as usual.
  • rankAttribute is always validated: an unknown attribute name returns 400 Bad Request with the reason. (Geometry and numeric field names are rejected the same way, via the generic not an indexed attribute for this account message.)
  • It applies to GET /api/Search and PUT /api/Search searches processed by the shape/visual backend — a file upload via PUT, or a fileUID input via GET — and is not applied to a pure free-text search (FT= with no file input) or when searchAssemblyComponents=true.
  • It also changes how ShapeResult.Score is calculated — see the next section.

When rankAttribute is set, ShapeResult.Score no longer contains the default shape-distance score. Each result’s score becomes:

Score = 1 − confidence (so: confidence = 1 − Score)

To show a % confidence to your users, compute (1 − Score) × 100 and round.

How to read the confidence:

  • confidence ranges from 0 to 1. Results are still sorted best-first: the lowest Score is the highest confidence.
  • The confidence is relative to the returned result set: it expresses how strongly each result stands out as the best match among the results returned, and the confidences of all returned results add up to 1 (100%).
  • If the search input has an exact match in the database, the top result’s confidence approaches 100% and all other results drop to near 0%.
  • If several results are almost equally similar, the confidence spreads out across them — a top confidence of, say, 9% is not an error; it means no single result clearly stands out.
  • Because trailing results can legitimately round to 0%, apply a display cutoff in your UI (for example, hide results below 1%) instead of showing every returned row.
  • Request more results (larger result count) and the confidence redistributes over the larger set — the values are only comparable within a single response.

Worked example — the top 3 rows of a 10-result response:

Score returnedconfidence = 1 − ScoreDisplay
0.3940.60661%
0.7690.23123%
0.9720.0283%

When rankAttribute is not set, Score keeps its default meaning: a distance where lower is better and 0.0 is an exact match.

GET /api/Search?filterStr=fileUID%3D11c20df1-8d84-418d-93ae-db3abb5d4d14&rankAttribute=category HTTP/1.1
Host: your-server.example.com
Authorization: Bearer USER_BEARER_TOKEN
import urllib.parse
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
file_uid = "11c20df1-8d84-418d-93ae-db3abb5d4d14"
filter_str = urllib.parse.quote(f"fileUID={file_uid}", safe="")
# Replace "category" with one of your own attribute names (see GET /api/FileAttributes).
# The API resolves it to attributes.category and returns one result per unique value.
resp = requests.get(
f"{server}/api/Search?filterStr={filter_str}&rankAttribute=category",
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(resp.json())

Typical top-level fields:

FieldMeaning
InputFileUIDUID of the temporary input file when a file-based search was used. For a multi-image search this is a comma-separated list of every input UID - see Searching with multiple images
InputThumbnailThumbnail URL for the input file
TotalResultsNumber of returned results
ResultsListResult objects
SearchFiltersRefinement filters that can be fed back into a future filterStr
SearchTimeSearch duration
MessageExtra message, often used for assembly-component cases

Typical result fields:

FieldMeaning
FileUIDResult file UID
FileNameResult file name
CompanyNameOwning company
ThumbnailURLThumbnail or shape image URL
ShapeResult.ScoreLower is better; 0.0 means exact match. When rankAttribute is set, the score is confidence-based instead: confidence = 1 − Score — see Confidence scores with rankAttribute
ShapeResult.ViewerUrlViewer URL when available
ShapeResult.LargeFileUrlLarge image URL
ShapeResult.AttributesResult attributes as name=value strings. A multi-valued attribute is returned pipe-separated exactly as uploaded (partid=REC-001|REC-002) — split on | to read the values. See Multi-valued attributes

Example response:

{
"InputFileUID": "example-uid",
"TotalResults": 2,
"ResultsList": [
{
"ShapeResult": {
"Score": 3.22989,
"Volume": 0.0,
"SurfaceArea": 0.0,
"ViewerUrl": "https://hoops.vizseek.com/view/viewer.html?...",
"DetailsUrl": "https://www.vizseek.com/ViewFile?...",
"LargeFileUrl": "https://viewfiles.vizseek.com/vizseek/.../lg.png?ver=3",
"Attributes": [
"document_type=Assembly",
"region=North America"
],
"FileHasThumbnail": true,
"FirstShapeMatchIndex": "1",
"FirstShapeMatchPageNum": "0",
"MatchPosition": "0,0,0.785398,0.472566",
"Name": "example-assembly.step"
},
"CompanyName": "Example Manufacturing",
"FileName": "example-assembly.step",
"FileUID": "00000000-0000-0000-0000-000000000000"
}
]
}

Use this to fetch the binary image or original file referenced by search results.

ItemValue
MethodGET
Path/api/Image
AuthNo bearer token. Requires the query-string token returned by search URLs.
ResponseRaw binary bytes with the file/image content type
ParameterRequiredNotes
fidYesFile UID to read from
tokenYesShort-lived image token returned by search URLs
typeNo0 = small thumbnail, 1 = large thumbnail, 2 = extracted shape, 3 = original/public file
shapeIndexNoApplies to type=0, type=1, and type=2 (selects the per-view small/large image or the extracted shape)
resp = requests.get(
f"{server}/api/Image?fid={file_uid}&type=1&token={image_token}"
)
resp.raise_for_status()
with open("result.png", "wb") as f:
f.write(resp.content)

Use this to send explicit feedback about search quality.

ItemValue
MethodPUT
Path/api/SearchFeedback
AuthUser bearer token
Content-Typeapplication/json
ResponsePlain feedback ID string
ParameterRequiredNotes
foundNotrue or false
rankNoRank of the expected result if found
expectationNoURL-encoded text, max 150 chars
helpfulnessNoInteger; the UI uses a 1-5 scale (the API does not reject out-of-range values)
userNoURL-encoded external user identifier, max 100 chars
inputFileUIDNoSearch input file UID from the search response
inputFileNameNoURL-encoded file name
otherInputNoURL-encoded extra search context
feedbackIdNoOptional GUID you want to control

The body is a JSON string containing a comma-separated result list in this format:

result_file_uid_1_score,result_file_uid_2_score

Example:

"17bc1ed1-1939-425f-bdfe-e59cbffaea16_1.05,10a5ba67-abfe-4b15-a085-e9f9a9952104_1.89"
import json
import requests
server = "https://your-server.example.com"
token = "USER_BEARER_TOKEN"
results_body = json.dumps(
"17bc1ed1-1939-425f-bdfe-e59cbffaea16_1.05,"
"10a5ba67-abfe-4b15-a085-e9f9a9952104_1.89"
)
resp = requests.put(
f"{server}/api/SearchFeedback?found=true&rank=1&helpfulness=5",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
data=results_body,
)
resp.raise_for_status()
print(resp.text)