Return Shapes
tell the AI exactly what each call gives back
Return Shapes
The single biggest source of AI mistakes is assuming a return shape. These are the actual shapes each call returns.
get_asset(asset_id) → dict | None
get_asset(asset_id) → dict | NoneKey fields: id, parentId, displayName, assetType (int; 1 = folder), plus storage
/ status fields. Returns None if not found / request fails.
asset = sdk.get_asset(asset_id)
asset["id"]; asset["parentId"]; asset["assetType"] # 1 == folderget_asset_child_nodes(...) → dict | None
get_asset_child_nodes(...) → dict | None{ "items": [ {id, displayName/name, assetType, hasChildren,...},... ], "totalItems": int }.
Page with page_index (0-based) and page_size; stop when a page has fewer than
page_size items.
create_folder_asset(parent_id, display_name) → dict | None
create_folder_asset(parent_id, display_name) → dict | None{ "id": "<new-folder-id>" }. The new folder id is result["id"].
upload_asset(...) → str | None (NOT a dict)
upload_asset(...) → str | None (NOT a dict)Returns the asset id as a bare string on success, None on failure.
asset_id = sdk.upload_asset(None, None, None, "replace", file_path, parent_id)
# asset_id is a string — do NOT do asset_id["id"]
Inconsistent with other create-style methods that return{id}.
move_asset(...) → dict | None
move_asset(...) → dict | None{ "items": [ {moved asset info} ], "totalItemCount": int }. The move is asynchronous:
the items[*] come back immediately as
{ "id", "assetId", "assetStatus": { "enumValue": 14, "description": "Moving",... } } — the
asset is still settling. Confirm the move by polling get_asset until parentId flips to
the destination, not by trusting the immediate response.
Request shape:
{ "targetIds": ["<asset-id>"], "actionArguments": { "destinationFolderAssetId": "<folder-id>" } }. SupplyingactionArguments.namerenames the asset during the move — the SDK'snamepositional maps to it.
delete_asset(asset_id) → dict | None
delete_asset(asset_id) → dict | NoneA confirmation response (HTTP 200) on success, None on failure. The operation is
permanent. Confirm by get_asset no longer resolving the id.
search(...)
search(...)- Python →
dict | None. - JavaScript → the same object (or
nullon request failure). Empty results return the object withitems: []— JS no longer collapses zero hits tofalse, so read fields directly and guard only againstnull.
Top-level shape:
{
"items": [ /* SearchResultModel objects */ ],
"totalItemCount": 9, // omit excludeTotalRecordCount to get this
"hasItems": true // present on the portal responses; cheap "any hits?" check
}
The wire keys aretotalItemCount/hasItems— notnextPageOffset/
totalRecordCount. Page by sending the nextpageOffset/pageSizeyourself (offset is zero-based); stop whenlen(items) < pageSizeorpageOffset*pageSize >= totalItemCount.
Each items[*] (SearchResultModel) carries flat metadata plus a nested identifiers
bag (the platform lower-cases its keys):
{
"id": "73d0…", "masterId": "73d0…",
"contentDefinitionId": "3ff2…", "contentDefinitionTitle": "Asset",
"title": "BaseContent",
"isRestricted": false, "isShared": false,
"createdDate": "2022-09-17T03:02:13Z",
"lastModifiedDate": "2024-03-30T20:04:52.47Z",
"permission": 3, "inheritSecurity": false, "contentLanguageStatus": 0,
"identifiers": {
"displayName": "BaseContent", "name": "BaseContent",
"parentId": "a732…", "assetType": 1, "displayPath": "BaseContent",
"createdDate": "2022-09-17T03:02:13Z"
}
}The folder/asset name and
parentIdlive underidentifiers, not at the top level — readitem["identifiers"]["displayName"]/["parentId"]. Selectingsearch_result_fields=[{"name": "identifiers.displayName"}](dotted) controls which of these are populated.
get_asset_details(asset_id) → dict | None
get_asset_details(asset_id) → dict | NoneThe full detail model. The id key is assetId, not id (unlike get_asset, which uses
id). Returns None if not found. Top-level keys:
{
"assetId": "7fad…",
"permission": 3, "isRestricted": false, "isShared": false, "inheritSecurity": true,
"parents": [ { "id": "...", "displayName": "...", "assetType": 5 }, ... ], // breadcrumb chain
"relatedAssets": [ { "id", "url", "fullUrl", "title", "metadataType", "mediaType", "contentLength" }, ... ],
"availability": [ { "availableStartDate": "...", "availableEndDate": "..." } ],
"availabilityStatus": 3
}details = sdk.get_asset_details(asset_id)
details["assetId"] # NOT details["id"]
get_asset→id;get_asset_details→assetId. Don't assume one from the other.
relatedAssets[*].fullUrlis a short-lived signed S3 URL (manifest/processor-jobs/ screenshots/transcripts) — fetch it promptly; don't cache it.
get_asset_parent_folders(asset_id, page_size) → dict | None
get_asset_parent_folders(asset_id, page_size) → dict | None{ "items": [ folder summaries ], "totalItemCount": int, "hasItems": bool }, root-first
(top-level folder → … → immediate parent). Each item is a folder-shaped SearchResultModel
(same identifiers bag as search), with identifiers.topLevelFolder: true on the root and
identifiers.parentId linking the chain. Useful for breadcrumbs.
create_placeholder_asset(parent_id, asset_name) → dict | None
create_placeholder_asset(parent_id, asset_name) → dict | None{ "id": "<new-asset-id>" }. The placeholder id is result["id"]. Uploading into that
placeholder (upload_asset(name, existing_asset_id=<placeholder-id>,...)) keeps the same
id through the upload — confirmed live: the asset settles under the original parentId.
update_asset_language(asset_id, language_id) → dict | None
update_asset_language(asset_id, language_id) → dict | NoneUpdated asset info on success, None on failure. Needs a real platform language_id. There
is no list-languages SDK verb — obtain the id via the search API, filtering on the
Languages content definition (contentDefinitionId Equals <languages-content-def-id>) and
reading the matching item's id. See recipes/search-patterns.md. May trigger reprocessing
of language-dependent AI metadata.
get_live_channels → list[dict] | None
get_live_channels → list[dict] | NoneA plain array of channel objects — not a {items} envelope. Iterate directly; an
empty deployment yields []. Each element is the same shape as get_live_channel below.
for ch in (sdk.get_live_channels() or []):
ch["id"]; ch["name"]; ch["status"]["description"] # status NAME, e.g. "Running"get_live_channel(live_channel_id) → dict | None
get_live_channel(live_channel_id) → dict | NoneThe full channel object; None if not found. The status is nested — the name is at
channel["status"]["description"], the lookup UUID at channel["status"]["id"]. Key
fields:
{
"id": "f6d0…", "name": "Studio A",
"type": { "id": "2bf0…", "description": "Normal" }, // LiveChannelType lookup
"status": { "id": "26a7…405", "description": "Running" }, // LiveChannelStatus lookup
"statusMessage": "…", "statusName": "Running",
"associatedInputs": [ /* input refs */ ],
"outputs": [ /* LiveChannelOutput */ ],
"scheduleEvents": [ /* LiveChannelEvent */ ],
"securityGroups": [ /* lookup items */ ]
}Map
status.description/type.description(or their.idUUIDs, or the int enums) viareference/enums.md. Python has no public status verb — readstatus.descriptionhere (JS exposesgetLiveChannelStatus, which returns exactly that, or"Deleted").
get_live_inputs / get_live_input(id) → dict | None
get_live_inputs / get_live_input(id) → dict | NoneA collection (list) / single live input. Each input carries id, name, type, its own
status + statusMessage, sources, and destinations — independent of channel status.
get_live_operators / get_live_operator(id) → dict | None
get_live_operators / get_live_operator(id) → dict | NoneA collection (list) / single live operator. The operator id is the live channel id; the object mirrors the channel's run-time status plus operator/segment metadata.
start_live_channel(...) / stop_live_channel(...) / live_channel_refresh → None
start_live_channel(...) / stop_live_channel(...) / live_channel_refresh → NoneNo return payload (the SDK methods are typed -> None); they raise on HTTP error. Confirm
the effect by re-reading get_live_channel(...)["status"]["description"] ("Running" after
start, "Idle" after stop). With wait_for_start/wait_for_stop, the call blocks until the
target status before returning.
create_content_group(name) / get_content_group(id) → dict | None
create_content_group(name) / get_content_group(id) → dict | NoneThe content group behind a UI collection (portal apiType only). The created object carries
its id; get_content_group returns the full record via the SDK's /api/contentGroup path:
Deployment note: some deployments do not expose the
contentGroup/portal/groupsroutes and return HTTP 404. The SDK swallows the 404 and returnsNone(no exception), socreate_content_groupyieldsNonewith no object created. Reads tolerateNone; run collections on a deployment where the portal routes are present.
// create_content_group("Test Collection") response:
{ "id": "85a8…", "name": "Test Collection", "sharedUsers": [], "contents": [] }
// after add_contents_to_content_group, each contents[] item is {id, title, properties}
// (NOT a {id, description} LookupItem):
{
"id": "<asset-uuid>", "title": "they_live.mp4",
"properties": {
"assetType": 2, "mediaTypeDisplay": "Video", "contentDefinitionTitle": "Asset",
"contentDefinitionId": "3ff2…", "createdDate": "5/14/2026 6:23:04 PM",
"previewImageUrl": "…", "previewImageFullUrl": "…"
}
}Membership lives under
contents, each item{id, title, properties}. Afteradd_contents_to_content_group, re-read and checkcontents[*].id. The create response carries only{id, name, sharedUsers, contents}— noshares/isShared/sequence(those are not in the response payload).
get_content_groups → dict | None
get_content_groups → dict | NoneThe caller's content groups. The SDK method is typed dict | None, but its own docstring
example iterates the result as a list of {id, name} lookups
(for group in groups: group["id"]) — a type-hint/docstring mismatch; treat the payload as a
list of group lookups and read group["id"]/group["name"]. On deployments without the portal
route this returns None (see the deployment note above). Owner = the authenticated portal user.
get_portal_groups(portal_groups) → dict | None
get_portal_groups(portal_groups) → dict | NoneAn object keyed by the requested bucket names (e.g. savedSearches, contentGroups,
sharedContentGroups) — pass a list of names, not ids.
content-group writes → None
Noneadd_contents_to_content_group, remove_contents_from_content_group, rename_content_group,
delete_content_group, share_content_group_with_users, and
stop_sharing_content_group_with_users are all typed -> None; they raise on HTTP error.
Confirm via a follow-up get_content_group (membership/name) or sharedUsers.
The API does return a body (the SDK discards it):
addreturns the full updated group (with populatedcontents), whileremovereturns only{ "id": "…" }. Don't rely on either — the Python methods returnNone, so re-read withget_content_group.
