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

Key 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 == folder

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

{ "id": "<new-folder-id>" }. The new folder id is result["id"].

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

{ "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>" } }. Supplying actionArguments.name renames the asset during the move — the SDK's name positional maps to it.

delete_asset(asset_id)dict | None

A confirmation response (HTTP 200) on success, None on failure. The operation is permanent. Confirm by get_asset no longer resolving the id.

search(...)

  • Pythondict | None.
  • JavaScript → the same object (or null on request failure). Empty results return the object with items: [] — JS no longer collapses zero hits to false, so read fields directly and guard only against null.

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 are totalItemCount / hasItems — not nextPageOffset /

totalRecordCount. Page by sending the next pageOffset/pageSize yourself (offset is zero-based); stop when len(items) < pageSize or pageOffset*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 parentId live under identifiers, not at the top level — read item["identifiers"]["displayName"] / ["parentId"]. Selecting search_result_fields=[{"name": "identifiers.displayName"}] (dotted) controls which of these are populated.

get_asset_details(asset_id)dict | None

The 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_assetid; get_asset_detailsassetId. Don't assume one from the other.

relatedAssets[*].fullUrl is 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

{ "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

{ "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

Updated 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_channelslist[dict] | None

A 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

The 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 .id UUIDs, or the int enums) via reference/enums.md. Python has no public status verb — read status.description here (JS exposes getLiveChannelStatus, which returns exactly that, or "Deleted").

get_live_inputs / get_live_input(id)dict | None

A 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

A 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_refreshNone

No 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

The 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/groups routes and return HTTP 404. The SDK swallows the 404 and returns None (no exception), so create_content_group yields None with no object created. Reads tolerate None; 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}. After add_contents_to_content_group, re-read and check contents[*].id. The create response carries only {id, name, sharedUsers, contents} — no shares/isShared/sequence (those are not in the response payload).

get_content_groupsdict | None

The 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

An object keyed by the requested bucket names (e.g. savedSearches, contentGroups, sharedContentGroups) — pass a list of names, not ids.

content-group writes → None

add_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): add returns the full updated group (with populated contents), while remove returns only { "id": "…" }. Don't rely on either — the Python methods return None, so re-read with get_content_group.