Appearance
Playlists
A playlist is an ordered list of clips. Once you create one, you can:
- Open it in the CTG video player at
https://pro.cleaningtheglass.com/video/<playlist_id>. - Read its clips and play them in your own player.
- Save it to your users' accounts so it appears in their saved playlists. You can also do this while creating it.
- Download its clips as MP4 files, a ZIP, or one combined video.
There are two ways to create a playlist:
| Method | Use it when |
|---|---|
POST /create_playlist/ctg_ids | You have marking IDs from the data feed |
POST /create_playlist/csv | You have games and times from another source |
From CTG Marking IDs
POST /create_playlist/ctg_idsCreates one clip for each data feed marking ID. Each clip starts a few seconds before the marking. For markings with a duration, such as chances, the clip runs through the end of the marking. For moments, such as shots, it runs a few seconds past it.
Clips are in the order you send the IDs. You can mix marking types in one request.
Supported IDs
| ID prefix | Example data feed fields |
|---|---|
chance_ | chance_id_ctg |
possession_ | possession_id_ctg |
shot_ | shot_id_ctg, linked_shot_id_ctg |
pass_ | pass_id_ctg |
possession_touch_ | possession_touch_id_ctg |
dribble_ | dribble_id_ctg |
drive_ | drive_id_ctg |
cut_ | cut_id_ctg |
closeout_ | closeout_id_ctg |
ball_screen_ | ball_screen_id_ctg |
off_ball_screen_ | off_ball_screen_id_ctg |
1v1_ | advantage_creation_1v1_id_ctg |
rebound_ | rebound_id_ctg |
turnover_ | turnover_id_ctg |
foul_ | foul_id_ctg, linked_foul_id_ctg |
free_throw_ | free_throw_id_ctg |
violation_ | violation_id_ctg |
jump_ball_ | jump_ball_id_ctg |
double_team_ | double_team_id_ctg |
contested_rebound_touch_ | contested_rebound_touch_id_ctg |
deflection_ | deflection_id_ctg |
jump_ | jump_id_ctg |
fall_ | fall_id_ctg |
Challenges and shot contests are not yet supported. To see one of those, use a related ID on the same row, such as the shot or chance ID. Violations without a violation_wall_clock, such as delay of game and lane violations, are returned in skipped_ids.
Request
Send JSON with one field, ids_string: the IDs separated by newlines (\n).
bash
curl --request POST \
--url 'https://api.cleaningtheglass.com/video/create_playlist/ctg_ids' \
--header 'X-API-KEY: YOUR_VIDEO_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"ids_string": "shot_8c0ae4ab89d51034a17dcf2cde9a179f\nchance_43b2166610037e91ab6fa6654fcdb0b8\nball_screen_7245fc8f21cbe594c94a4c29875a76fa"}'Response
| Field | Type | Description |
|---|---|---|
result | string | Success or an error message |
playlist_id | UUID | ID of the new playlist |
num_clips | integer | Number of clips created |
playlist_clips | list | The clips, in the same format as Read a Playlist |
skipped_ids | list | Only present when some IDs were skipped |
An ID is skipped when it has an unsupported prefix, does not exist, or its game or period has no video. The other IDs still become clips.
From a CSV
POST /create_playlist/csvCreates one clip for each row of a CSV with these columns:
game_id,period,start_time,start_padding,end_padding| Column | Description |
|---|---|
game_id | NBA game ID (game_id_nba, such as 0022400121) or video game ID from /available_game_periods. See Game IDs |
period | Period number |
start_time | Game clock as seconds left (581) or mm:ss (9:41), or a timestamp in epoch milliseconds |
start_padding | Seconds of video before the start time |
end_padding | Seconds of video after the start time |
Values of 3600 or less are read as a game clock. Larger values are read as a timestamp. You can mix both in one CSV, and mix NBA game IDs and video game IDs across rows. A header row is optional; its first column can be game_id or game_id_nba.
When the period's wall_clock_timing_available is true in /available_game_periods, game clocks and timestamps are converted to each other, so clips play correctly either way. Otherwise, use game clocks.
Request
Send JSON with one field, csv_string: the rows separated by newlines (\n).
bash
curl --request POST \
--url 'https://api.cleaningtheglass.com/video/create_playlist/csv' \
--header 'X-API-KEY: YOUR_VIDEO_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"csv_string": "00124539-08e3-4174-bd7f-db5e96a904fb,1,9:41,10,5\n00124539-08e3-4174-bd7f-db5e96a904fb,1,1730330093050,10,5"}'Both rows show the same moment, once by game clock and once by timestamp.
Response
| Field | Type | Description |
|---|---|---|
result | string | Success or an error message |
playlist_id | UUID | ID of the new playlist |
num_clips | integer | Number of clips created |
playlist_clips | list | The clips, in the same format as Read a Playlist |
Each row's clip_number is its position in the CSV. Rows whose game and period have no video are skipped, which leaves a gap in the clip numbers. If any row is for a league your key cannot access, no playlist is created and the response is 403.
Save to Users While Creating
Both create methods can also save the new playlist to your users in the same request. Add these fields to the request body:
| Field | Type | Description |
|---|---|---|
user_emails | list | Up to 50 email addresses. Required if you send any of these fields |
playlist_name | string | The saved playlist's name, up to 120 characters. Required if you send any of these fields |
collaborative | boolean | Optional. Defaults to false. See Save a Playlist to Users |
The users and the copies work as described in Save a Playlist to Users.
bash
curl --request POST \
--url 'https://api.cleaningtheglass.com/video/create_playlist/csv' \
--header 'X-API-KEY: YOUR_VIDEO_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"csv_string": "00124539-08e3-4174-bd7f-db5e96a904fb,1,9:41,10,5", "playlist_name": "Week 3 turnovers", "user_emails": ["analyst@example.com", "coach@example.com"]}'Your users are checked before the playlist is created. If any email is not an active CTG Pro user in your organization, or the user cannot receive saved playlists, the response is 400 and no playlist is created. Its results list the emails that failed, in the format below.
When the request succeeds, the response is the usual create response plus a results entry for each email, in the format described under Save a Playlist to Users.
If something unexpected goes wrong while saving, the response is 500. The playlist exists, so the response still has its playlist_id, and each entry in results says which users got a copy. Call /save_playlist_for_users with that playlist_id and only the emails that failed. If the request produces no clips, the playlist is created but not saved, and the response is 400.
Read a Playlist
GET /playlist/<playlist_id>Returns a playlist and its clips.
Response
| Field | Type | Description |
|---|---|---|
playlist.id | UUID | Playlist ID |
playlist.date_created | date | Date the playlist was created |
clips | list | The clips, in playlist order |
available_3d_game_ids_nba | list | NBA game IDs in the playlist that have CTG 3D animations |
Each clip has these fields:
| Field | Type | Description |
|---|---|---|
clip_number | integer | Clip number within the playlist. Use it for downloads |
game_id | UUID | Video game ID |
game_id_nba | string or null | NBA game ID |
game_info | object | id, game_id_nba, league, game_date, home_team, away_team |
league | string | NBA, GLEAGUE, or NBASL |
period | integer | Period number |
start_game_clock | number | Seconds left in the period at the start time |
start_timestamp | integer or null | Start time in epoch milliseconds. null when the video has no timestamps |
start_padding | number | Seconds before the start time |
end_padding | number | Seconds after the start time |
Pass a clip's league, game_id (or game_id_nba), period, start time, and padding to /clip to play it in your own player.
Example
json
{
"playlist": {
"id": "9fbe7116-240d-4715-8019-e1b0fccb644d",
"date_created": "2026-10-13"
},
"clips": [
{
"clip_number": 1,
"game_id": "00124539-08e3-4174-bd7f-db5e96a904fb",
"game_id_nba": "0022400121",
"game_info": {
"away_team": "ATL",
"game_date": "2024-10-30",
"game_id_nba": "0022400121",
"home_team": "WAS",
"id": "00124539-08e3-4174-bd7f-db5e96a904fb",
"league": "NBA"
},
"league": "NBA",
"period": 1,
"start_game_clock": 581,
"start_timestamp": 1730330093050,
"start_padding": 10,
"end_padding": 5
}
],
"available_3d_game_ids_nba": ["0022400121"]
}Save a Playlist to Users
POST /save_playlist_for_usersSaves a copy of a playlist to the saved playlists of each user you list, as if they had opened it in the CTG video player and saved it themselves. Each user gets their own copy, so changes one user makes do not affect anyone else's. Users appear with the playlist the next time they open CTG Pro.
You can only save to active CTG Pro users in your organization. Your key also needs access to every league in the playlist.
Request
Send JSON with these fields:
| Field | Type | Description |
|---|---|---|
playlist_id | UUID | Required. The ID of a playlist you created or read |
playlist_name | string | Required. The saved playlist's name, up to 120 characters. If a user already has a playlist with this name, their copy gets a number, such as Week 3 turnovers (1) |
user_emails | list | Required. Up to 50 email addresses |
collaborative | boolean | Whether other users in your organization who open a copy can edit it. Defaults to false, so they can open it but not edit it |
bash
curl --request POST \
--url 'https://api.cleaningtheglass.com/video/save_playlist_for_users' \
--header 'X-API-KEY: YOUR_VIDEO_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"playlist_id": "9fbe7116-240d-4715-8019-e1b0fccb644d", "playlist_name": "Week 3 turnovers", "user_emails": ["analyst@example.com", "coach@example.com"]}'Response
| Field | Type | Description |
|---|---|---|
result | string | Success when every user got a copy, Partial when only some did, or an error message |
results | list | One entry per email, in the order you sent them |
Each entry in results has these fields:
| Field | Type | Description |
|---|---|---|
email | string | The email address, as you sent it |
status | string | saved or error |
name | string | The name the copy was saved under. Only when status is saved |
saved_playlist_id | UUID | The Film Room saved playlist ID for that user's copy. Open it at https://pro.cleaningtheglass.com/video/saved/<saved_playlist_id>. Only when status is saved |
error | string | Why no copy was saved. Only when status is error |
json
{
"result": "Partial",
"results": [
{
"email": "analyst@example.com",
"status": "saved",
"name": "Week 3 turnovers",
"saved_playlist_id": "318e7de6-7242-44da-b05a-d2ce902c331b"
},
{
"email": "coach@example.com",
"status": "error",
"error": "No active CTG Pro user with this email in your organization"
}
]
}A user's entry is an error when:
- The email is not an active CTG Pro user in your organization. The message is the same whether or not an account exists elsewhere.
- The user does not have the access needed to save this playlist.
- More than one account uses the email. Contact CTG to resolve it.
One user's error does not stop the others from being saved. The response is 200 when at least one copy was saved. It is 400 when none was or the request is invalid. It is 403 when your key is not linked to a customer organization, or when the playlist has clips from a league your key cannot access. It is 404 when the playlist does not exist or has no clips, and 500 when no copy could be saved because of a server error, in which case you can retry the same request.
CTG Video Player
Open any playlist in the CTG video player:
https://pro.cleaningtheglass.com/video/<playlist_id>The player does not require a login, so you can send playlist links to anyone. Viewers can select clips from the list next to the video or use these keyboard shortcuts:
| Key | Action |
|---|---|
| Space | Play or pause |
| ↑ / ↓ | Previous or next clip |
| ← / → | Back or forward 2 seconds |
s | Cycle speed: 0.25x, 0.5x, 1x |
l | Loop the current clip instead of moving to the next one |
f | Toggle full screen |
c | Copy a link to the current clip |
