From a81d3cadd5a4f5240f7bc6142098bb0dfa2c9305 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 12:54:06 -0700 Subject: [PATCH 1/9] Introduce SCREENLY_API_BASE_URL constant for API URLs --- automated_quality_control.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index 7ec179c..f6e6690 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -8,6 +8,7 @@ from retry import retry API_TOKEN = os.environ.get("SCREENLY_API_TOKEN") +SCREENLY_API_BASE_URL = "https://api.screenlyapp.com" REQUEST_HEADERS = { "Authorization": f"Token {API_TOKEN}", "Content-Type": "application/json", @@ -21,7 +22,7 @@ def get_team_id() -> str: Return the current account's team ID. """ response = requests.get( - "https://api.screenlyapp.com/v4.1/teams?is_current=eq.true", + f"{SCREENLY_API_BASE_URL}/v4.1/teams?is_current=eq.true", headers=REQUEST_HEADERS, ) response.raise_for_status() @@ -37,7 +38,7 @@ def get_ten_random_assets(team_id: str) -> List[str]: Filtering by team_id ensures the token has permission to add them to playlists. """ response = requests.get( - f'https://api.screenlyapp.com/v4/assets?select=id&team_id=eq.{team_id}&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")', + f'{SCREENLY_API_BASE_URL}/v4/assets?select=id&team_id=eq.{team_id}&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")', headers=REQUEST_HEADERS, ) response.raise_for_status() @@ -54,7 +55,7 @@ def get_screens() -> List[Dict[str, Any]]: Return a list of screens in the account. """ - response = requests.get('https://api.screenlyapp.com/v4/screens?select=id,name,hostname,status,in_sync&type=eq.hardware&is_enabled=eq.true', headers=REQUEST_HEADERS) + response = requests.get(f'{SCREENLY_API_BASE_URL}/v4/screens?select=id,name,hostname,status,in_sync&type=eq.hardware&is_enabled=eq.true', headers=REQUEST_HEADERS) response.raise_for_status() return response.json() @@ -100,7 +101,7 @@ def get_qc_playlist_ids(): Get all playlists starting with 'PLAYLIST_PREFIX'. """ - response = requests.get("https://api.screenlyapp.com/v4/playlists", headers=REQUEST_HEADERS) + response = requests.get(f"{SCREENLY_API_BASE_URL}/v4/playlists", headers=REQUEST_HEADERS) response.raise_for_status() qc_playlists = [] @@ -117,19 +118,19 @@ def delete_playlist(playlist_id): items must be removed before the playlist itself can be deleted. """ labels_response = requests.delete( - f"https://api.screenlyapp.com/v4/labels/playlists?playlist_id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4/labels/playlists?playlist_id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) if not labels_response.ok: return False items_response = requests.delete( - f"https://api.screenlyapp.com/v4/playlist-items?playlist_id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4/playlist-items?playlist_id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) if not items_response.ok: return False response = requests.delete( - f"https://api.screenlyapp.com/v4/playlists?id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4/playlists?id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) return response.ok @@ -140,7 +141,7 @@ def get_all_screens_label_id(): Return the ID of the built-in 'all-screens' label. """ response = requests.get( - "https://api.screenlyapp.com/v4/labels?type=eq.all-screens", + f"{SCREENLY_API_BASE_URL}/v4/labels?type=eq.all-screens", headers=REQUEST_HEADERS, ) response.raise_for_status() @@ -160,7 +161,7 @@ def add_asset_to_playlist(playlist_id, asset_id): "duration": 10, } response = requests.post( - "https://api.screenlyapp.com/v4/playlist-items", + f"{SCREENLY_API_BASE_URL}/v4/playlist-items", headers={**REQUEST_HEADERS, "Prefer": "return=representation"}, json=payload, ) @@ -178,7 +179,7 @@ def assign_playlist_to_all_screens(playlist_id): "playlist_id": playlist_id, } response = requests.post( - "https://api.screenlyapp.com/v4/labels/playlists", + f"{SCREENLY_API_BASE_URL}/v4/labels/playlists", headers={**REQUEST_HEADERS, "Prefer": "return=representation, resolution=ignore-duplicates"}, json=payload, ) @@ -201,7 +202,7 @@ def create_qc_playlist(): } response = requests.post( - "https://api.screenlyapp.com/v4/playlists", + f"{SCREENLY_API_BASE_URL}/v4/playlists", headers={**REQUEST_HEADERS, "Prefer": "return=representation"}, json=payload, ) From aa786ef2f531cf3ccc3e796b67de96f7a46a1088 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 15:18:21 -0700 Subject: [PATCH 2/9] Migrate assets endpoint to v4.1 --- automated_quality_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index f6e6690..1505907 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -38,7 +38,7 @@ def get_ten_random_assets(team_id: str) -> List[str]: Filtering by team_id ensures the token has permission to add them to playlists. """ response = requests.get( - f'{SCREENLY_API_BASE_URL}/v4/assets?select=id&team_id=eq.{team_id}&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")', + f'{SCREENLY_API_BASE_URL}/v4.1/assets?select=id&team_id=eq.{team_id}&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")', headers=REQUEST_HEADERS, ) response.raise_for_status() From 97dee29a144d3a2f8e5c2f1fc684589aa7f937b3 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 15:38:04 -0700 Subject: [PATCH 3/9] Migrate screens endpoint to v4.1 --- automated_quality_control.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index 1505907..b37eb6f 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -52,12 +52,23 @@ def get_ten_random_assets(team_id: str) -> List[str]: def get_screens() -> List[Dict[str, Any]]: """ - Return a list of screens in the account. + Return a list of screens in the account. In v4.1, status and in_sync + live in the screen_statuses view, so they are embedded and flattened + into each screen dict. """ - response = requests.get(f'{SCREENLY_API_BASE_URL}/v4/screens?select=id,name,hostname,status,in_sync&type=eq.hardware&is_enabled=eq.true', headers=REQUEST_HEADERS) + response = requests.get( + f'{SCREENLY_API_BASE_URL}/v4.1/screens?select=id,name,hostname,screen_statuses(status,in_sync)&type=eq.hardware&is_enabled=eq.true', + headers=REQUEST_HEADERS, + ) response.raise_for_status() - return response.json() + + screens = response.json() + for screen in screens: + screen_status = screen.pop("screen_statuses") or {} + screen["status"] = screen_status.get("status", "offline") + screen["in_sync"] = screen_status.get("in_sync", False) + return screens @retry(AssertionError, tries=10, delay=SCREEN_SYNC_THRESHOLD / 10) From aa35797b5f1c92afe238eec1251bb7aea1368b7b Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 15:49:20 -0700 Subject: [PATCH 4/9] Migrate playlists endpoint to v4.1 --- automated_quality_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index b37eb6f..e274e46 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -112,7 +112,7 @@ def get_qc_playlist_ids(): Get all playlists starting with 'PLAYLIST_PREFIX'. """ - response = requests.get(f"{SCREENLY_API_BASE_URL}/v4/playlists", headers=REQUEST_HEADERS) + response = requests.get(f"{SCREENLY_API_BASE_URL}/v4.1/playlists", headers=REQUEST_HEADERS) response.raise_for_status() qc_playlists = [] From 0a315afb5855dbe47bc371671f88d0a4b24b526e Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 15:55:30 -0700 Subject: [PATCH 5/9] Migrate delete_playlist endpoints to v4.1 --- automated_quality_control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index e274e46..d678294 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -129,19 +129,19 @@ def delete_playlist(playlist_id): items must be removed before the playlist itself can be deleted. """ labels_response = requests.delete( - f"{SCREENLY_API_BASE_URL}/v4/labels/playlists?playlist_id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4.1/labels/playlists?playlist_id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) if not labels_response.ok: return False items_response = requests.delete( - f"{SCREENLY_API_BASE_URL}/v4/playlist-items?playlist_id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4.1/playlist-items?playlist_id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) if not items_response.ok: return False response = requests.delete( - f"{SCREENLY_API_BASE_URL}/v4/playlists?id=eq.{playlist_id}", + f"{SCREENLY_API_BASE_URL}/v4.1/playlists?id=eq.{playlist_id}", headers=REQUEST_HEADERS, ) return response.ok From ca86994eac9e91cc3fd4750ad7ecb9331fa1f162 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 16:00:34 -0700 Subject: [PATCH 6/9] Migrate labels endpoint to v4.1 --- automated_quality_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index d678294..7324288 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -152,7 +152,7 @@ def get_all_screens_label_id(): Return the ID of the built-in 'all-screens' label. """ response = requests.get( - f"{SCREENLY_API_BASE_URL}/v4/labels?type=eq.all-screens", + f"{SCREENLY_API_BASE_URL}/v4.1/labels?type=eq.all-screens", headers=REQUEST_HEADERS, ) response.raise_for_status() From 07c423024bce82a6af1cf115cedd19e74f62cbb5 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 16:05:18 -0700 Subject: [PATCH 7/9] Migrate add_asset_to_playlist endpoint to v4.1 --- automated_quality_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index 7324288..d5d668a 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -164,7 +164,7 @@ def get_all_screens_label_id(): def add_asset_to_playlist(playlist_id, asset_id): """ - Add a single asset to a playlist via the v4 playlist-items endpoint. + Add a single asset to a playlist via the v4.1 playlist-items endpoint. """ payload = { "playlist_id": playlist_id, @@ -172,7 +172,7 @@ def add_asset_to_playlist(playlist_id, asset_id): "duration": 10, } response = requests.post( - f"{SCREENLY_API_BASE_URL}/v4/playlist-items", + f"{SCREENLY_API_BASE_URL}/v4.1/playlist-items", headers={**REQUEST_HEADERS, "Prefer": "return=representation"}, json=payload, ) From 5c554c224f561d71a49eb77fa04a6dea272ff99b Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 16:07:45 -0700 Subject: [PATCH 8/9] Migrate assign_playlist_to_all_screens endpoint to v4.1 --- automated_quality_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index d5d668a..4cbd453 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -190,7 +190,7 @@ def assign_playlist_to_all_screens(playlist_id): "playlist_id": playlist_id, } response = requests.post( - f"{SCREENLY_API_BASE_URL}/v4/labels/playlists", + f"{SCREENLY_API_BASE_URL}/v4.1/labels/playlists", headers={**REQUEST_HEADERS, "Prefer": "return=representation, resolution=ignore-duplicates"}, json=payload, ) From 8231078dcc3be5c67ea85f7c105451a871027cf0 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Thu, 9 Jul 2026 16:12:30 -0700 Subject: [PATCH 9/9] Migrate create_qc_playlist endpoint to v4.1 --- automated_quality_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/automated_quality_control.py b/automated_quality_control.py index 4cbd453..0cf8af1 100644 --- a/automated_quality_control.py +++ b/automated_quality_control.py @@ -213,7 +213,7 @@ def create_qc_playlist(): } response = requests.post( - f"{SCREENLY_API_BASE_URL}/v4/playlists", + f"{SCREENLY_API_BASE_URL}/v4.1/playlists", headers={**REQUEST_HEADERS, "Prefer": "return=representation"}, json=payload, )