Recipe: real-world search patterns
Recipe: real-world search patterns
Recipe: real-world search patterns
Prompt exampleFind the folder's contents, look up one asset by id, list only the top-level folders, return just the display names, page through a large result set, and resolve a language_id from the Languages content definition.
search is the most-used call. These are common patterns, expressed against the search
component. All are read-only, Class A - to confine, scope on parentId.
The endpoint is POST /api/{admin|portal}/search, chosen by the session's apiType. Both SDKs
expose the full 16-parameter surface (free-text, filters, sort, paging, field-select, plus the
advanced modes below); there is no SDK ceiling - older 6-param docs were stale.
Key facts (see reference/return-shapes.md + enums.md)
- The result is
{items, totalItemCount, hasItems}. Empty results return the object with
items: []in both languages; guard only
againstNone/null. - A result's name/parent live under
item["identifiers"], not at the top level. - Filters are
{fieldName, operator, values}; AND-combined by default.operatoris
Equals(the most common),Contains,Prefix,NotEquals, … (seeenums.md). offsetis zero-based; page untillen(items) < sizeor you covertotalItemCount.
Python
# component: search (def search(sdk, query=None, filters=None, sort_fields=None,
# size=None, offset=None, search_result_fields=None, *,
# similar_asset_id=None, min_score=None, use_llm_search=None,
# search_text_fields=None, filter_binder=None, ...))
def folder_contents(sdk, folder_id, size=100):
"""Direct children of a folder - the parentId-Equals pattern."""
flt = [{"fieldName": "parentId", "operator": "Equals", "values": folder_id}]
return (search(sdk, filters=flt, size=size) or {}).get("items", [])
def find_by_id(sdk, asset_id):
"""Fetch one record by exact id (the portal id-lookup pattern)."""
flt = [{"fieldName": "uuidSearchField", "operator": "Equals", "values": asset_id}]
items = (search(sdk, filters=flt) or {}).get("items", [])
return items[0] if items else None
def folder_descendants(sdk, folder_id, size=100):
"""Everything *below* a folder, recursively (children, grandchildren, ...).
parentId-Equals returns only direct children, so a full tree otherwise means
walking it level by level (see iter_children below). The performant one-shot
is uuidSearchField-Equals: it matches the folder and every asset whose ancestry
includes it. We drop the folder itself so the result is strictly its contents."""
flt = [{"fieldName": "uuidSearchField", "operator": "Equals", "values": folder_id}]
items = (search(sdk, filters=flt, size=size) or {}).get("items", [])
return [i for i in items if i.get("id") != folder_id]
def iter_children(sdk, folder_id):
"""Iterative fallback: walk the tree breadth-first via parentId-Equals.
Acceptable for small trees, or when you must page a huge tree node-by-node;
it issues one query per folder, so prefer folder_descendants() above. Each
visited id is re-queried for its own children until the frontier drains."""
stack, out = [folder_id], []
while stack:
flt = [{"fieldName": "parentId", "operator": "Equals", "values": stack.pop()}]
for item in (search(sdk, filters=flt, size=100) or {}).get("items", []):
out.append(item)
stack.append(item.get("id"))
return out
def top_level_folders(sdk, content_definition_id):
"""Only the roots of the tree, for one content type."""
flt = [
{"fieldName": "contentDefinitionId", "operator": "Equals", "values": content_definition_id},
{"fieldName": "assetType", "operator": "Equals", "values": 1},
{"fieldName": "topLevelFolder", "operator": "Equals", "values": "true"},
]
return (search(sdk, filters=flt, size=100) or {}).get("items", [])
def names_only(sdk, folder_id):
"""Return just display names - select fields with dotted paths."""
flt = [{"fieldName": "parentId", "operator": "Equals", "values": folder_id}]
fields = [{"name": "identifiers.displayName"}, {"name": "identifiers.assetType"}]
res = search(sdk, filters=flt, search_result_fields=fields, size=100) or {}
return [i.get("identifiers", {}).get("displayName") for i in res.get("items", [])]
def iter_all(sdk, filters, page_size=100):
"""Yield every item across pages (offset is zero-based)."""
offset = 0
while True:
page = search(sdk, filters=filters, size=page_size, offset=offset) or {}
items = page.get("items", [])
for item in items:
yield item
if len(items) < page_size:
return
offset += 1 # offset is in PAGES (page index), not records
def sorted_by_name(sdk, folder_id):
"""Folder contents sorted A→Z by display name (sortType, not sortOrder)."""
flt = [{"fieldName": "parentId", "operator": "Equals", "values": folder_id}]
srt = [{"fieldName": "displayName", "sortType": "Ascending"}]
return (search(sdk, filters=flt, sort_fields=srt, size=100) or {}).get("items", [])
def find_language_id(sdk, languages_content_definition_id, display_name):
"""Resolve a language_id for update_asset_language via the Languages content def.
There is no list-languages SDK verb; search the Languages content definition
and read the matching record's id. languages_content_definition_id is the
stable per-deployment content definition for languages (discover once, reuse).
"""
flt = [{"fieldName": "contentDefinitionId", "operator": "Equals",
"values": languages_content_definition_id}]
items = (search(sdk, filters=flt, size=200) or {}).get("items", [])
for item in items:
if (item.get("identifiers") or {}).get("displayName") == display_name:
return item.get("id")
return NoneJavaScript
// component: search(sdk, query, filters, sortFields, size, offset, searchResultFields, opts)
// opts = { similarAssetId, minScore, useLlmSearch, searchTextFields, filterBinder, ... }
export async function folderContents(sdk, folderId, size = 100) {
const flt = [{ fieldName: "parentId", operator: "Equals", values: folderId }];
const res = await search(sdk, null, flt, null, size);
return res ? res.items : []; // res is the object (items: []) when empty; null only on failure
}
export async function findById(sdk, assetId) {
const flt = [{ fieldName: "uuidSearchField", operator: "Equals", values: assetId }];
const res = await search(sdk, null, flt);
return res && res.items.length ? res.items[0] : null;
}
export async function folderDescendants(sdk, folderId, size = 100) {
// One-shot recursive load: the folder and everything beneath it. Preferred over
// walking parentId level by level (see iterChildren). Drop the folder itself.
const flt = [{ fieldName: "uuidSearchField", operator: "Equals", values: folderId }];
const res = await search(sdk, null, flt, null, size);
const items = res ? res.items : [];
return items.filter((i) => i.id !== folderId);
}
export async function iterChildren(sdk, folderId) {
// Iterative fallback: breadth-first over parentId. Acceptable for small trees;
// one query per folder, so prefer folderDescendants() when you can.
const stack = [folderId], out = [];
while (stack.length) {
const flt = [{ fieldName: "parentId", operator: "Equals", values: stack.pop() }];
const res = await search(sdk, null, flt, null, 100);
for (const item of res ? res.items : []) {
out.push(item);
stack.push(item.id);
}
}
return out;
}
export async function topLevelFolders(sdk, contentDefinitionId) {
const flt = [
{ fieldName: "contentDefinitionId", operator: "Equals", values: contentDefinitionId },
{ fieldName: "assetType", operator: "Equals", values: 1 },
{ fieldName: "topLevelFolder", operator: "Equals", values: "true" },
];
const res = await search(sdk, null, flt, null, 100);
return res ? res.items : [];
}
export async function namesOnly(sdk, folderId) {
const flt = [{ fieldName: "parentId", operator: "Equals", values: folderId }];
const fields = [{ name: "identifiers.displayName" }, { name: "identifiers.assetType" }];
const res = await search(sdk, null, flt, null, 100, null, fields);
return res ? res.items.map((i) => i.identifiers?.displayName) : [];
}
export async function findLanguageId(sdk, languagesContentDefinitionId, displayName) {
// Resolve a languageId for updateAssetLanguage via the Languages content def.
// No list-languages SDK verb; search the Languages content definition instead.
const flt = [{ fieldName: "contentDefinitionId", operator: "Equals",
values: languagesContentDefinitionId }];
const res = await search(sdk, null, flt, null, 200);
const items = res ? res.items : [];
const match = items.find((i) => i.identifiers?.displayName === displayName);
return match ? match.id : null;
}Advanced search
Operator and field-bucket tables are in reference/enums.md.
Python
def find_similar(sdk, asset_id, min_score=0.65, size=20):
"""Vector find-similar: items most like `asset_id`. Ignores query/offset;
needs vector search enabled (else an empty result with a 'not enabled' message)."""
return (search(sdk, similar_asset_id=asset_id, min_score=min_score, size=size)
or {}).get("items", [])
def llm_search(sdk, question, size=20):
"""LLM/deep semantic search over a natural-language query (segment-level for video)."""
return (search(sdk, query=question, use_llm_search=True, size=size)
or {}).get("items", [])
def search_transcripts(sdk, phrase, size=50):
"""Scope free-text to subtitles/transcripts only (see SearchTextFields in enums.md)."""
return (search(sdk, query=phrase, search_text_fields=["Transcripts"], size=size)
or {}).get("items", [])
def created_in_range(sdk, folder_id, start_utc, end_utc):
"""Range operators on a date field (UTC YYYY-MM-DDTHH:MM:SS.SSSZ); AND-combined."""
flt = [
{"fieldName": "parentId", "operator": "Equals", "values": folder_id},
{"fieldName": "createdDate", "operator": "GreaterThanEquals", "values": start_utc},
{"fieldName": "createdDate", "operator": "LessThan", "values": end_utc},
]
return (search(sdk, filters=flt, size=100) or {}).get("items", [])
def name_prefix_or_substring(sdk, folder_id, term):
"""Non-Equals string ops: Prefix (starts-with) + Contains (token match), OR-combined.
Use filter_binder=1 so the two name predicates are ORed instead of ANDed."""
flt = [
{"fieldName": "displayName", "operator": "Prefix", "values": term},
{"fieldName": "displayName", "operator": "Contains", "values": term},
]
return (search(sdk, filters=flt, filter_binder=1, size=100) or {}).get("items", [])
def exact_name_or_missing(sdk, name):
"""Exact (case-sensitive) match via the .keyword sub-field; also keep records that
have no displayName at all (includeNull)."""
flt = [{"fieldName": "displayName.keyword", "operator": "Equals",
"values": name, "includeNull": True}]
return (search(sdk, filters=flt, size=100) or {}).get("items", [])
def distinct_videos_with_clips(sdk, query, size=50):
"""Collapse duplicates on masterId, return clip/segment rows, skip the (costly) total,
and compute signed URLs only for the preview field."""
return (search(sdk, query=query, size=size,
distinct_on_field_name="masterId",
include_video_clips=True,
full_url_field_names=["previewImageUrl"],
exclude_total_record_count=True) or {}).get("items", [])JavaScript
// Advanced options ride on the trailing opts object; the core positional args are unchanged.
export async function findSimilar(sdk, assetId, minScore = 0.65, size = 20) {
const res = await search(sdk, null, null, null, size, null, null,
{ similarAssetId: assetId, minScore });
return res ? res.items : [];
}
export async function llmSearch(sdk, question, size = 20) {
const res = await search(sdk, question, null, null, size, null, null,
{ useLlmSearch: true });
return res ? res.items : [];
}
export async function searchTranscripts(sdk, phrase, size = 50) {
const res = await search(sdk, phrase, null, null, size, null, null,
{ searchTextFields: ["Transcripts"] });
return res ? res.items : [];
}
export async function namePrefixOrSubstring(sdk, term) {
// OR the two name predicates with filterBinder = 1 (0/unset = AND).
const flt = [
{ fieldName: "displayName", operator: "Prefix", values: term },
{ fieldName: "displayName", operator: "Contains", values: term },
];
const res = await search(sdk, null, flt, null, 100, null, null, { filterBinder: 1 });
return res ? res.items : [];
}
export async function distinctVideosWithClips(sdk, query, size = 50) {
const res = await search(sdk, query, null, null, size, null, null, {
distinctOnFieldName: "masterId",
includeVideoClips: true,
fullUrlFieldNames: ["previewImageUrl"],
excludeTotalRecordCount: true,
});
return res ? res.items : [];
}Operator gotcha: range operators (
LessThan/GreaterThan/…) andPrefixare not
valid against an array ofvalues- the API returns 400. Use a single value per range
predicate (combine with multiple filters as above).
Notes
- Direct
sdk.searchcalls are fully positional. Thesearch(sdk,...)wrappers shown
above accept keyword args, but the underlyingsdk.searchmethod has 15 positional
parameters with no keyword support. When writing standalone scripts that call the SDK
directly, pass all arguments positionally and useNonefor unused slots:
sdk.search(query, offset, size, filters, sort_fields, search_result_fields,
similar_asset_id, min_score, exclude_total_record_count, filter_binder,
full_url_field_names, distinct_on_field_name, include_video_clips,
use_llm_search, include_internal_fields_in_results)EqualsondisplayNameis fuzzy, not exact. It matches any record whose display
name contains the search term -- not an exact string equality. To find a folder by its
precise name, search byparentIdto get the candidate list, then filter client-side:
# Find root-level "Content" folder exactly
ROOT_ID = "00000000-0000-0000-0000-000000000000"
flt = [{"fieldName": "parentId", "operator": "Equals", "values": ROOT_ID}]
items = sdk.search(None, 0, 200, flt, None, None, None, None, None, None, None, None, None, None, None)
content = next((i for i in (items or {}).get("items", [])
if (i.get("identifiers") or {}).get("displayName","").strip() == "Content"), None)parentId Equalsis the workhorse for "what's directly in this folder?";uuidSearchField Equalsis the workhorse for "give me this id and everything under it" - a leaf id matches
just that record, a folder id matches its whole subtree.- Direct children vs. whole subtree:
parentId Equalslists only immediate children. To
load everything below a folder you can either walk the tree level by level (the iterative
iter_children/iterChildrenabove - acceptable for small trees, but one query per folder)
or, preferably, issue a singleuuidSearchField Equals <folder-id>query that returns the
folder and all of its descendants at once. The recursive one-shot is the performant choice;
the iterative walk is the fallback when you must page a huge tree or the field is unavailable. url Equalsis a prefix match, not an exact match. Searchingurl Equals "Content/Photo/Events/"returns the folder and every asset inside it. To locate a specific folder, navigate viaparentId Equalson the parent's id instead.filter_bindercontrols AND vs OR. Default (null/omitted) is AND - all filters must match.filter_binder: 1(Python:filter_binder=1) switches to OR - any filter match returns the record. ThenamePrefixOrSubstring/name_prefix_or_substringpatterns above use OR deliberately; most other patterns should leave it at the default AND.- Pagination uses a keyset cursor.
nextPageOffset/next_page_offsetis a sort-key token returned by the server - pass it back verbatim. When calling the raw API or JS SDK directly, usenextPageOffsetfrom the response rather than incrementing a counter manually. - Search index has a short consistency lag. Newly created/moved/deleted assets may not appear in (or disappear from) search results for several seconds to minutes. When you already hold an id (e.g. from a create response), verify existence with a direct
getorurl-filter lookup rather than a full paginated scan - direct lookups bypass the index lag. moveAsset/move_assetis asynchronous. The call returns a success response immediately, but the move is queued and executed in the background. Searching for the asset at its new URL right after a successful move will fail - it still appears at the old URL until the operation completes. To confirm a move finished, pollurl Equals <new-path>(oruuidSearchField Equals <id>and check the returned URL) until the asset appears at the destination.- Newly created/uploaded assets are searchable only after indexing - there is a short lag
before they appear in results, so poll (or read directly viaget/list_childrenwhen
you already hold the id). - Use
sortType(Ascending/Descending), notsortOrder. - Resolving a
language_id: there is no list-languages SDK verb. Filter search on the
Languages content definition (contentDefinitionId Equals <languages-content-def-id>) and
read the matching item'sid- this is the idupdate_asset_languageexpects. The
Languages content definition id is stable per deployment; discover it once and reuse it.
Updated 27 days ago
