Clusters
Episode clusters (an episode plus its clips and posts), cluster reports and timelines, and the AI cluster builder.
- GETList clusters
- GETCluster report
- POSTCreate a cluster
- POSTMerge episode clusters
- GETRetrieve a cluster
- PATCHUpdate a cluster
- DELETEDelete a cluster
- GETCluster timeline
- POSTAdd posts to an episode's cluster
- POSTRemove cluster nodes
- POSTSet cluster node tags
- POSTRefresh a cluster's metrics
- GETAccounts the cluster builder searches
- PATCHUpdate the cluster builder's accounts
- GETEpisodes to cluster
- GETFind an episode's clips and posts
- POSTStart an AI cluster build
- GETRetrieve a show's latest cluster build
- GETRetrieve a cluster build
- GETList cluster suggestions
- POSTReview cluster suggestions
List clusters
/v1/clustersEpisode clusters across shows as the Clusters page lists them: reach by category and form, node, clip and post counts, type (episode, merged, manual), emptiness, freshness, sponsor brands, linked campaigns and public URL, plus meta.totals over reach-contributing clusters. Rows come from stored summaries (summary_stale marks rows that could not be recomputed).
time_range (default all) buckets by the cluster's episode publish date. series, guests and format match the episode's tags; custom_tags matches node tags (applied to at most 300 rows). state is all, active, empty, merged or manual. brand matches sponsored-node or linked-campaign brands.
SDKlistener.clusters.list()client.clusters.list()
Query parameters
- Limit to these show ids (default: every show in the workspace). Repeat the parameter or comma-separate values.
show_idsarray of strings - 7d | 14d | 30d | 90d | 180d | 365d | all, or a quarter like 2026-Q2. Windows are UTC days anchored to today.
time_rangestringDefault
"all" - First day, YYYY-MM-DD (inclusive). With end_date, overrides time_range.
start_datestring - Last day, YYYY-MM-DD (inclusive). With start_date, overrides time_range.
end_datestring - Series tags. Repeat the parameter or comma-separate values.
seriesarray of strings - Guest tags. Repeat the parameter or comma-separate values.
guestsarray of strings - Format tags. Repeat the parameter or comma-separate values.
formatarray of strings - Repeat the parameter or comma-separate values.
custom_tagsarray of strings - Brand name or slug filter.
brandstring statestringOne ofallactiveemptymergedmanualDefault
"all"- Case-insensitive text filter.
searchstring - Sort field.
sort_bystringOne ofpublished_attotal_reachyoutube_reachaudio_reachsocial_reachnode_countlast_refreshed_atnameDefault
"published_at" - asc or desc.
sort_orderstringOne ofascdescDefault
"desc" - Page size, 1–100 (default 25).
limitintegerDefault
25 - Opaque cursor from the previous page's next_cursor.
cursorstring
Returns
200OK
objectstringrequiredurlstringrequireddataarray of objectsrequiredShow child attributesHide child attributes
has_morebooleanrequired- Pass as
next_cursorstringrequiredcursorto get the next page; null on the last page. - The result's non-list fields (window, coverage, freshness, totals, note …).
metaobjectrequiredShow child attributesHide child attributes
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Cluster report
/v1/clusters/reportCluster analytics per show and for the network over a window, as the Episodes-page KPI bar computes them: reach by category (YouTube long-form and Shorts, audio, social) and form, cluster count, per-cluster averages, growth against the previous window, reach distribution, content mix and cluster counts by type.
This is cluster reach (the clips and posts attached to episodes), a different number from the account-level GET /v1/analytics/reach. Clusters are bucketed by their episode's publish date; merged clusters are excluded unless include_merged=true and never double-counted with their sources. Growth is null when the previous window had no clusters.
Windows. time_range is 7d, 14d, 30d, 90d, 180d, 365d, all (rolling UTC days, today inclusive) or a calendar quarter such as 2026-Q2 (a bare Q2 means the current year). start_date + end_date (YYYY-MM-DD, inclusive, both required) override it. Results carry window and coverage.
Growth. Growth compares against the equal-length window immediately before (the previous quarter for quarter tokens). It is null (show it as "—", never 0) when the previous window has under 50 % day coverage. Windows longer than 30 days read stored history and compare like-for-like: only platforms whose data covers at least half of the previous window, both windows summed over those platforms (coverage.growth_platforms).
SDKlistener.clusters.report()client.clusters.report()
Query parameters
- Limit to these show ids (default: every show in the workspace). Repeat the parameter or comma-separate values.
show_idsarray of strings - Only shows whose categories contain one of these (case-insensitive substring). Repeat the parameter or comma-separate values.
categoriesarray of strings - 7d | 14d | 30d | 90d | 180d | 365d | all, or a quarter like 2026-Q2. Windows are UTC days anchored to today.
time_rangestringDefault
"30d" - First day, YYYY-MM-DD (inclusive). With end_date, overrides time_range.
start_datestring - Last day, YYYY-MM-DD (inclusive). With start_date, overrides time_range.
end_datestring include_mergedbooleanDefault
false
Returns
200OK
- The resolved analytics window (from `
windowobjectrequiredcore.dates.Window.as_dict()`).Show child attributesHide child attributes
- `
networkobjectrequiredbuildClusterSummaryFromRows` for one window (the episodes-page KPI bar).Show child attributesHide child attributes
showsarray of objectsrequiredShow child attributesHide child attributes
show_idsarray of stringsrequired- How much of the window actually has data (design §2).
coverageobjectrequiredShow child attributesHide child attributes
computed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequiredinclude_mergedbooleanDefault
falsecategoriesarray of stringsfreshnessmap of objectShow child attributesHide child attributes
stale_platformsarray of stringsmissing_show_idsarray of strings- Cluster rows whose stored summary could not be verified and were not recomputed.
summary_stale_countintegerDefault
0 notestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Create a cluster
/v1/shows/{show_id}/clustersCreate a cluster for a show: an episode cluster (one id in episode_ids), a merged cluster (two or more ids) or a manual cluster (manual: {name, …}, no episode ids). Episode ids must be episodes of the show. content_nodes optionally attaches clips and posts (platform label such as "YouTube Shorts", url, optional type, title, thumbnail, views, likes, comments, date). An existing cluster id is never overwritten; a merged cluster first creates any missing per-episode clusters.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusters.create()client.shows.clusters.create()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- Episode ids on the show.
episode_idsarray of strings - Name.
namestring internal_titlestringcontent_nodesarray of objectsmanualobjectShow child attributesHide child attributes
- EMV rates: {long_form_cpm, short_form_cpm, like_rate, comment_rate}.
emv_ratesobjectShow child attributesHide child attributes
- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Merge episode clusters
/v1/shows/{show_id}/clusters/mergeBuild, or rebuild, the merged cluster for several episodes from their episode clusters. Source clusters are never modified; missing ones are created first. Rebuilding keeps the merged cluster's EMV rates, customization, internal title, brand collaboration and creation date.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusters.merge()client.shows.clusters.merge()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- Episode ids on the show.
episode_idsarray of stringsrequired - Name.
namestring - true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Retrieve a cluster
/v1/shows/{show_id}/clusters/{cluster_id}One cluster in full: metadata (type, episodes, customization, brand collaboration), 14 reach and engagement metrics computed from its nodes, every content node (platform, URL, reach, category, form, engagement, publish date, custom tags, sponsor, parent_id / child_ids canvas links), the canvas edges, subcluster rollups, custom-tag rollups, linked campaigns, the EMV estimate with the rate precedence used, the first-30-days estimate, refresh freshness and errors, and share URLs.
Nodes are sorted by reach and capped at node_limit (nodes_truncated says when more exist); edges and subclusters always cover the whole cluster. Rebuild the canvas tree by grouping nodes on parent_id.
SDKlistener.shows.clusters.retrieve()client.shows.clusters.retrieve()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Query parameters
include_nodesbooleanDefault
truenode_limitintegerDefault
200
Returns
200OK
clusterobjectrequiredShow child attributesHide child attributes
- The 14 reach / engagement fields `
metricsobjectrequiredcalculateClusterMetricsFromNodes` returns.Show child attributesHide child attributes
metrics_sourcestringrequiredfirst_30_daysobjectrequiredShow child attributesHide child attributes
emvobjectrequiredShow child attributesHide child attributes
- Content nodes (episode / clip / post) on the document.
node_countintegerrequired tag_rollupsobjectrequiredShow child attributesHide child attributes
- The cluster's subclusters, as the canvas Subclusters list and the Cluster Analytics 'Subcluster' tab show them.
subclustersobjectrequiredShow child attributesHide child attributes
freshnessobjectrequiredShow child attributesHide child attributes
computed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequirednodesarray of objectsShow child attributesHide child attributes
nodes_truncatedbooleanDefault
falsenodes_returnedintegerDefault
0- Every canvas edge between content nodes, never truncated by node_limit, so parent links resolve even for nodes not returned.
edgesarray of objectsShow child attributesHide child attributes
sponsor_brandsarray of stringsbrandsarray of stringslinked_campaignsarray of objectsShow child attributesHide child attributes
last_refreshed_atstringlast_refresh_attempt_atstringlast_refresh_sourcestringrefresh_errorsarray of objectsShow child attributesHide child attributes
refresh_error_countintegerDefault
0platform_freshnessmap of objectShow child attributesHide child attributes
stale_platformsarray of stringsnotestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Update a cluster
/v1/shows/{show_id}/clusters/{cluster_id}Update cluster metadata: name, internal title, description, image, cover, date, public-page customization, emv_rates and brand_collab. Only the fields you send change. emv_rates merges over the current rates; customization merges over the stored page overrides, and customization.co_brand ({name, logo_url, href}, https URLs) adds a partner co-brand to the public cluster page ("co_brand": null removes it); clear_customization, clear_internal_title and clear_brand_collab remove those fields. Description, image, cover and date on non-manual clusters last only until the next canvas save in the app.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusters.update()client.shows.clusters.update()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- Name.
namestring internal_titlestringclear_internal_titlebooleanDefault
false- Free-text description.
descriptionstring imagestringcoverstringdatestring- Public cluster page overrides (`
customizationobjectclusterCustomization.js`). Only the keys you send change.Show child attributesHide child attributes
clear_customizationbooleanDefault
false- EMV rates: {long_form_cpm, short_form_cpm, like_rate, comment_rate}.
emv_ratesobjectShow child attributesHide child attributes
brand_collabobjectShow child attributesHide child attributes
clear_brand_collabbooleanDefault
false- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Delete a cluster
/v1/shows/{show_id}/clusters/{cluster_id}Permanently delete a cluster (no undo) and report, without fixing, the campaigns and shared reports that still reference it. Send ?dry_run=true first to see them. A manual cluster's synthetic episode is removed too. Clusters spanning several episodes are refused unless allow_merged=true.
- Send
?dry_run=trueto get{"object": "dry_run", "would_write": …}without writing anything. - Requires
?confirm=true; without it the API answers 409confirmation_requiredwith the exactpreview.
SDKlistener.shows.clusters.delete()client.shows.clusters.delete()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Query parameters
allow_mergedbooleanDefault
false- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false - Must be true to apply destructive or metered changes; without it the API answers 409 with a preview.
confirmbooleanDefault
false
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Cluster timeline
/v1/shows/{show_id}/clusters/{cluster_id}/timelineThe cluster page's "views / engagement over time" curve (granularity daily, weekly or monthly; chart_type cumulative or per period). It spreads the cluster's lifetime totals from its earliest episode date to today on a fixed decay, so synthetic is always true; for measured dates use the nodes' published_at from GET /v1/shows/{show_id}/clusters/{cluster_id}.
SDKlistener.shows.clusters.timeline()client.shows.clusters.timeline()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Query parameters
granularitystringOne ofdailyweeklymonthlyDefault
"daily"- daily values or a cumulative running total.
chart_typestringOne ofdailycumulativeDefault
"cumulative"
Returns
200OK
- The minimal show reference embedded in cross-show results.
showobjectrequiredShow child attributesHide child attributes
cluster_idstringrequiredtitlestringrequiredgranularitystringrequiredOne ofdailyweeklymonthlychart_typestringrequiredOne ofdailycumulative- True: the app's shape-only curve (lifetime totals spread over time), not measured data.
syntheticbooleanrequired start_datestringrequiredstart_date_from_episodebooleanrequiredtotal_daysintegerrequireddata_pointsintegerrequiredtotal_viewsinteger | numberrequiredtotal_engagementinteger | numberrequiredpointsarray of objectsrequiredShow child attributesHide child attributes
computed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequirednotestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Add posts to an episode's cluster
/v1/shows/{show_id}/episodes/{episode_id}/postsAdd clips or posts to an episode's cluster by URL (YouTube, Shorts, TikTok, Instagram, Facebook, X, Threads, LinkedIn and the show's own Substack posts), including posts from accounts that are not connected. Each link is fetched (title, thumbnail, views, likes, comments) and added as a node; the cluster is created when the episode has none. Links already in the cluster are skipped. Up to 25 URLs per request. A dry run checks every link (supported platform, duplicates, whether a cluster will be created) without fetching.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.episodes.addPosts()client.shows.episodes.add_posts()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Episode id (see GET /v1/shows/{show_id}/episodes).
episode_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- Post or clip URLs (up to 25).
urlsarray of stringsrequired - true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Remove cluster nodes
/v1/shows/{show_id}/clusters/{cluster_id}/nodes/removeRemove nodes, and every edge touching them, from a cluster. The last episode node cannot be removed; to remove the whole cluster use DELETE /v1/shows/{show_id}/clusters/{cluster_id}.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusters.nodes.remove()client.shows.clusters.nodes.remove()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- Cluster node ids.
node_idsarray of stringsrequired - true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Refresh a cluster's metrics
/v1/shows/{show_id}/clusters/{cluster_id}/refreshRe-fetch every clip and post of one cluster from its platforms and return per-platform node counts, the provider-call estimate and the result. Needs "confirm": true (one metered provider call per node). Merged clusters are refused; refresh their source clusters. A node whose fresh value reads 0 keeps its previous value.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything. - Requires
"confirm": true; without it the API answers 409confirmation_requiredwith the exactpreview.
SDKlistener.shows.clusters.refresh()client.shows.clusters.refresh()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Cluster id (see GET /v1/clusters).
cluster_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false - Must be true to apply destructive or metered changes; without it the API answers 409 with a preview.
confirmbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Accounts the cluster builder searches
/v1/shows/{show_id}/cluster-sourcesThe accounts the cluster builder searches for a show's clips and posts: connected accounts, accounts learned from the show's past clusters (with their share of past nodes) and accounts a person added, each with platform, handle, role (own, partner, guest) and on/off, plus the date window around each episode. The first request for a show learns the list from its past clusters (no cluster is modified). configured=false means the builder is not reachable from this server.
SDKlistener.shows.clusterSources.retrieve()client.shows.cluster_sources.retrieve()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Returns
200OK
show_idstringrequiredshow_namestringrequiredconfiguredbooleanrequiredcomputed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequiredhandlesarray of objectsShow child attributesHide child attributes
enabled_countintegerDefault
0- Enabled handles per platform.
by_platformmap of integer windowobjectShow child attributesHide child attributes
learned_atstringupdated_atstringapp_urlstringnotestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Update the cluster builder's accounts
/v1/shows/{show_id}/cluster-sourcesChange which accounts the cluster builder searches for a show: add accounts (profile URL, platform:handle or {handle, platform, role}), remove accounts a person added, or enable / disable any account (platform:handle, @handle or a profile URL). Connected and learned accounts can only be turned off. Added accounts default to role partner; use own for the show's brand accounts and guest for a guest. Takes effect on the next build or content search.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusterSources.update()client.shows.cluster_sources.update()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/jsonaddarray of objectsShow child attributesHide child attributes
removearray of stringsenablearray of stringsdisablearray of strings- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Episodes to cluster
/v1/shows/{show_id}/cluster-candidatesThe show's episodes as the cluster builder sees them, newest first, including the newest episodes from the live feed that the episodes list may not have yet: id, title, date, whether it has a cluster, its build status (ready = suggestions waiting) and source (stored or live). query keeps titles containing every word; from_date / to_date bound the publish date. Not paginated.
SDKlistener.shows.clusterCandidates.list()client.shows.cluster_candidates.list()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Query parameters
- Search text.
querystring - Earliest publish date, YYYY-MM-DD.
from_datestring - Latest publish date, YYYY-MM-DD.
to_datestring - Maximum number of items to return.
limitintegerDefault
30
Returns
200OK
objectstringrequiredurlstringrequireddataarray of objectsrequiredShow child attributesHide child attributes
has_morebooleanrequired- Pass as
next_cursorstringrequiredcursorto get the next page; null on the last page. - The result's non-list fields (window, coverage, freshness, totals, note …).
metaobjectrequiredShow child attributesHide child attributes
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Find an episode's clips and posts
/v1/shows/{show_id}/episodes/{episode_id}/content-candidatesFind the clips and posts that belong to one episode, including external posts not in the Listener library: searches the show's connected accounts, every enabled account in its cluster sources and any extra handles, then an AI judge scores each candidate (confidence, sure / likely, reason) and flags the ones already in the cluster.
handles adds accounts for this search only (x:northwindaudio, tiktok:@name, a profile URL, or @name with platforms). query (a guest, a topic) is a ranking hint, never a filter; judge=false skips the scoring (faster). from_date / to_date override the window around the episode date. Writes nothing, but external accounts can spend scraper credits on their first fetch of a month. Add the posts you pick with POST /v1/shows/{show_id}/episodes/{episode_id}/posts.
SDKlistener.shows.episodes.contentCandidates()client.shows.episodes.content_candidates()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Episode id (see GET /v1/shows/{show_id}/episodes).
episode_idstringrequired
Query parameters
- Search text.
querystring - Repeat the parameter or comma-separate values.
handlesarray of strings - Only these platforms (e.g. youtube, spotify, instagram; aliases accepted). Repeat the parameter or comma-separate values.
platformsarray of stringsOne oftiktokinstagramfacebookxyoutubethreadslinkedin - Earliest publish date, YYYY-MM-DD.
from_datestring - Latest publish date, YYYY-MM-DD.
to_datestring - Maximum number of items to return.
limitintegerDefault
30 judgebooleanDefault
true
Returns
200OK
show_idstringrequiredshow_namestringrequiredconfiguredbooleanrequiredcomputed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequiredepisodeobjectShow child attributesHide child attributes
- Set when episode_title matched several episodes: pick one and call again with its id.
candidatesarray of objectsShow child attributesHide child attributes
itemsarray of objectsShow child attributesHide child attributes
totalintegerDefault
0library_countintegerDefault
0external_countintegerDefault
0sure_countintegerDefault
0in_cluster_countintegerDefault
0- Extra accounts passed in for this search.
searched_handlesarray of strings - Every account searched, with posts found and any fetch error.
searchedarray of objectsShow child attributesHide child attributes
- Days searched before/after the episode date.
windowobjectShow child attributesHide child attributes
notestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Start an AI cluster build
/v1/cluster-buildsBuild episode clusters with AI for a period or chosen episodes: for every episode in scope the builder gathers clips and posts from the show's connected and other accounts, judges which belong to the episode, and records suggestions (mode=suggest, default) or writes the sure ones straight into clusters (mode=auto).
Send "dry_run": true first: would_write.data.estimate gives episodes in scope, already clustered, to build, minutes and scraper credits. The real request starts a background job (data.job.job_id); follow it with GET /v1/cluster-builds/{job_id}, then review with GET /v1/shows/{show_id}/cluster-suggestions. scope: week, last30, range (from_date + to_date), backcatalog (up to 500 episodes per job) or episodes (episode_ids). Clustered episodes are skipped unless force=true. One job runs per show; starting another returns the running one (data.already_running).
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.clusterBuilds.create()client.cluster_builds.create()
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/json- A show id in this workspace (see GET /v1/shows).
show_idstringrequired scopestringOne ofweeklast30rangebackcatalogepisodesDefault
"week"- Earliest publish date, YYYY-MM-DD.
from_datestring - Latest publish date, YYYY-MM-DD.
to_datestring - Episode ids on the show.
episode_idsarray of strings - How the change applies (see the operation description).
modestringOne ofsuggestautoDefault
"suggest" forcebooleanDefault
false- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error
Retrieve a show's latest cluster build
/v1/cluster-builds/latestThe show's most recent cluster build: status, done / total, current phase and episode, counts (built, ready to review, nothing found, failed, skipped), per-episode outcomes, a one-line summary and the app link where suggestions are reviewed. Never cached; wait_seconds (up to 50) returns as soon as the job progresses.
SDKlistener.clusterBuilds.latest()client.cluster_builds.latest()
Query parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired wait_secondsintegerDefault
0
Returns
200OK
show_idstringrequiredshow_namestringrequiredconfiguredbooleanrequiredcomputed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequiredjobobjectShow child attributesHide child attributes
- True once the job is done, failed or cancelled; stop polling then.
finishedbooleanDefault
false - One plain-English line to relay to the user.
summarystringDefault
"" outcomesarray of objectsShow child attributesHide child attributes
- App page where the suggestions are reviewed.
review_urlstring notestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Retrieve a cluster build
/v1/cluster-builds/{job_id}One cluster build job (from POST /v1/cluster-builds): status, done / total, current phase and episode, counts (built, ready to review, nothing found, failed, skipped), per-episode outcomes, a one-line summary and the review link. Never cached. To follow a running job pass wait_seconds (up to 50): the request returns as soon as the job progresses or finishes.
SDKlistener.clusterBuilds.retrieve()client.cluster_builds.retrieve()
Path parameters
- Cluster build job id returned by POST /v1/cluster-builds.
job_idstringrequired
Query parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired wait_secondsintegerDefault
0
Returns
200OK
show_idstringrequiredshow_namestringrequiredconfiguredbooleanrequiredcomputed_atstringrequiredcache_age_secondsnumberrequiredobjectstringrequiredjobobjectShow child attributesHide child attributes
- True once the job is done, failed or cancelled; stop polling then.
finishedbooleanDefault
false - One plain-English line to relay to the user.
summarystringDefault
"" outcomesarray of objectsShow child attributesHide child attributes
- App page where the suggestions are reviewed.
review_urlstring notestring
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
List cluster suggestions
/v1/shows/{show_id}/cluster-suggestionsEpisodes with AI cluster suggestions, newest first (status=ready by default), each with its suggested clips and posts: key, platform, title, URL, handle, source (library or external), confidence, label (sure / likely), reason and metrics. Other statuses: built, accepted, nothing, failed, building, dismissed, all. items_per_episode=0 returns counts only. Not paginated; has_more says when more episodes wait.
SDKlistener.shows.clusterSuggestions.list()client.shows.cluster_suggestions.list()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired
Query parameters
- Status filter or value.
statusstringOne ofreadybuildingbuiltacceptednothingfaileddismissedallDefault
"ready" - Maximum number of items to return.
limitintegerDefault
20 items_per_episodeintegerDefault
25
Returns
200OK
objectstringrequiredurlstringrequireddataarray of objectsrequiredShow child attributesHide child attributes
has_morebooleanrequired- Pass as
next_cursorstringrequiredcursorto get the next page; null on the last page. - The result's non-list fields (window, coverage, freshness, totals, note …).
metaobjectrequiredShow child attributesHide child attributes
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found429rate_limited500internal_error
Review cluster suggestions
/v1/shows/{show_id}/cluster-suggestions/{episode_id}/reviewAccept or dismiss one episode's AI suggestions. Send accept_keys (item keys from GET /v1/shows/{show_id}/cluster-suggestions) and/or accept_all_sure=true, or dismiss=true, never both. Accepted posts are added to the episode's cluster (created when missing); dismiss drops every remaining suggestion. Unknown keys are an error listing the valid ones.
- Send
"dry_run": trueto get{"object": "dry_run", "would_write": …}without writing anything.
SDKlistener.shows.clusterSuggestions.review()client.shows.cluster_suggestions.review()
Path parameters
- A show id in this workspace (see GET /v1/shows).
show_idstringrequired - Episode id (see GET /v1/shows/{show_id}/episodes).
episode_idstringrequired
Headers
- Any unique string (≤ 255 chars). Replays within 24 h return the stored response with Idempotent-Replayed: true.
Idempotency-Keystring
Request body
application/jsonaccept_keysarray of stringsaccept_all_surebooleanDefault
falsedismissbooleanDefault
false- true returns exactly what would be written without writing anything (the API's test mode).
dry_runbooleanDefault
false
Returns
200OK
One of:
WriteResult
DryRun
Errors
400parameter_invalid401credential_invalid403scope_missing404resource_not_found409confirmation_required413body_too_large429rate_limited500internal_error