Media library filters
Filter, count and total the media library - the same filters the Zentag Studio library uses.
GET /media is the unified library: clips, highlights and streams under one
shape. It accepts the same filters as the Zentag Studio media library, so a
partner UI can offer the same browsing experience.
Three endpoints share one filter vocabulary:
| Endpoint | Answers |
|---|---|
GET /media | Which items match? |
GET /media/stats | How many, how long, what average rating? |
GET /media/filters/counts | Which filter values are still worth offering? |
Send the same parameters to all three and they describe the same selection.
That is what lets you render a list, a header total, and a filter sidebar that
greys out dead ends, from one piece of UI state.
How filters combine
Every list-valued filter is comma-separated. Within one parameter, values
are OR-ed; across parameters, they are AND-ed.
?players=p1,p2&rating=5
means (player p1 OR p2) AND rated 5.
Filter reference
Scope
| Parameter | Notes |
|---|---|
search | Case-insensitive substring match on title |
stream_id | Restrict to one or more streams |
streams_only | Return stream rows instead of the media inside them |
media_types | Subset of clip, highlight, stream |
Media attributes
| Parameter | Notes |
|---|---|
aspect_ratio | e.g. 16:9,9:16 |
clip_status | Processing status, e.g. COMPLETED |
download_status | Download state |
ai_event_type | AI-detected event type |
category | |
platform | Destination the item was published to |
source | How the item was created |
rating | Star ratings, e.g. 4,5 |
tags | Tag names |
Sport context
| Parameter | Notes |
|---|---|
players | Player tag ids, from the players facet |
actions | Action tag ids, from the actions facet |
competition, season | Names |
matches, match_date, match_day | |
teams, venues | Names |
players and actions take ids, not names. Read them from the id field of
the corresponding bucket in /media/filters/counts and pass them straight back.
Time and ordering
| Parameter | Notes |
|---|---|
start_date, end_date | Creation date range, inclusive, YYYY-MM-DD |
sort_by | latest (default), oldest, rating, duration, title |
Hierarchy fast-path
| Parameter | From facet |
|---|---|
hierarchy_competition_ref | hierarchy_competitions |
hierarchy_season_id | hierarchy_seasons |
hierarchy_round_id | hierarchy_rounds |
hierarchy_matchday_id | hierarchy_matchdays |
These filter on structural ids rather than names. Prefer them for a drill-down
UI (competition → season → round → matchday): they are faster than the
equivalent name-based filters and immune to naming inconsistencies.
Building a filter sidebar
GET /media/filters/counts returns each filter's selectable values with the
number of items behind each, computed against the filters you already applied.
Values that would return nothing are absent, so you can hide or disable them.
{
"object": "media_filter_counts",
"aspect_ratios": [
{ "value": "16:9", "count": 812 },
{ "value": "9:16", "count": 194 }
],
"players": [
{ "id": "665f...", "name": "A. Rossi", "count": 47 }
],
"facet_pagination": {
"players": { "total": 312, "has_more": true, "page": 1, "page_size": 20, "offset": 0,
"load_more_key": "players:2" }
}
}Note the two shapes: most facets are { value, count }, while the tag-backed
players and actions are { id, name, count }.
Buckets are optional
Read every bucket defensively. A bucket appears only when it was computed,
and two parameters deliberately narrow that:
counts_bundlecomputes only one named group of facets. Use it to keep
sidebar refreshes cheap instead of recomputing everything on each click.facet_load_morereturns a single bucket's next page, so the response
contains just that one bucket.
Paging within a facet
A facet with hundreds of values is paginated on its own. facet_pagination
reports the state per bucket; when has_more is true, send its load_more_key
back as facet_load_more to fetch the next page of that one facet without
recomputing the rest.
facet_page_size and facet_page control this directly if you would rather
drive it yourself.
The match index
Pass include_match_index=true to also receive match_index: the matches in
the current selection, with names, dates, teams and venues. This drives a
"jump to match" control.
Each entry carries a match_ref - a stable, opaque handle. It is safe to store
and compare across requests, but it is not a third-party provider's match id and
cannot be resolved against one. See Identifiers.
Showing totals
GET /media/stats returns the totals for the current selection in one request,
which is far cheaper than paging the whole result set to count it:
{
"object": "media_stats",
"total_items": 1204,
"total_duration_seconds": 22344,
"total_duration_label": "6h 12m",
"average_rating": 4.1,
"processing_count": 3,
"clip_count": 1150,
"highlight_count": 54,
"stream_count": 12,
"stream_media_count": 1204
}Pagination
Paginate with cursor, not page. The cursor is opaque - pass next_cursor
back verbatim, and don't parse it. Changing any filter or sort_by invalidates
the cursor; restart from the first page. See Pagination.
Identifiers
Every id returned here is a Zentag identifier. Zentag ingests fixtures, squads
and match data from third-party sports data providers, but provider-internal
identifiers are deliberately never exposed through this API - not in media
items, not in tag metadata, and not in facet counts.
Player tag metadata is therefore a closed set: player_name, short_name,
position, jersey_number, nationality and roster_memberships. Do not
write code expecting other keys to appear, and do not attempt to join Zentag ids
against another provider's data.
Updated about 2 months ago
