Recipe: real-world search patterns

Recipe: real-world search patterns

Recipe: real-world search patterns

💡

Prompt example

Find 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 against None/null.
  • A result's name/parent live under item["identifiers"], not at the top level.
  • Filters are {fieldName, operator, values}; AND-combined by default. operator is Equals (the most common), Contains, Prefix, NotEquals, … (see enums.md).
  • offset is zero-based; page until len(items) < size or you cover totalItemCount.

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

JavaScript

// 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 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/…) and Prefix are not valid against an array of values — the API returns 400. Use a single value per range predicate (combine with multiple filters as above).

Notes

  • parentId Equals is the workhorse for "what's in this folder?"; uuidSearchField Equals is the workhorse for "give me exactly this id".
  • Newly created/uploaded assets are searchable only after indexing — there is a short lag before they appear in results, so poll (or read directly via get / list_children when you already hold the id).
  • Use sortType (Ascending/Descending), not sortOrder.
  • 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's id — this is the id update_asset_language expects. The Languages content definition id is stable per deployment; discover it once and reuse it.