Skip to main content

How to organize recordings with folders and tags

Folders and tags keep a growing number of recordings easy to find, and folders decide who gets to see what. This guide shows how to find folders, file recordings, share folders with users and teams, and label recordings and appointments with tags, with a complete script at the end.

Every example works as it stands once SALLY_TOKEN and DIRECTORY_ID are set, see the Quickstart. Pick your language once, every example on the API pages follows.

Quick navigation

  1. Find folders
  2. Add and remove recordings
  3. List the recordings of a folder
  4. Rename or move a folder
  5. Share folders
  6. Tags
  7. Complete script
  8. Endpoints at a glance

1. Find folders​

Folders form a tree: a folder can have a parent (parentFolderId), and a recording can be added to a folder and removed again. GET/v1.0/directories/{directoryId}/folders returns your personal, shared and team folders in one paged list.

ParameterTypeDefaultDescription
searchstringCase-insensitive substring match on the folder name.
parentRecordingFolderIdstringOnly the direct children of this folder. Leave it out for all folders.
pageinteger1Page number, starting at 1.
pageSizeinteger25Entries per page, at most 100.

This request looks for folders whose name contains "sales":

curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders?search=sales" \
-H "Authorization: Bearer $SALLY_TOKEN"
200 OK
{
"page": 1,
"pageSize": 25,
"total": 1,
"hasMore": false,
"items": [
{
"folderId": "f0186448-90aa-4df8-a13d-17a832c35c28",
"name": "Sales calls",
"description": "Customer calls of the sales team",
"ownerUserId": "eece96ba-87a6-4f16-a07d-a7720d671312",
"parentFolderId": null,
"isOwner": true,
"isFavorite": false,
"accessLevel": "readWrite",
"recordingsCount": 3,
"createdOn": "2026-09-24T09:30:00Z",
"modifiedOn": "2026-09-24T09:30:00Z"
}
]
}
FieldTypeMeaning
folderIdstringId of the folder.
namestringFolder display name.
descriptionstring or nullFolder description, null if unset.
ownerUserIdstringId of the user who owns the folder.
parentFolderIdstring or nullId of the parent folder, null for a top-level folder.
isOwnerbooleantrue if you own the folder.
isFavoritebooleantrue if you marked the folder as favorite.
accessLevelstringYour effective access: none, read or readWrite.
recordingsCountnumberNumber of recordings in the folder.
createdOnstringCreation timestamp (ISO 8601).
modifiedOnstringLast modification timestamp (ISO 8601).
Note

POST/v1.0/directories/{directoryId}/folders/search takes the same filters (search, parentRecordingFolderId, page, pageSize) in the request body, see Search endpoints.

2. Add and remove recordings​

PUT/v1.0/directories/{directoryId}/folders/{folderId}/recordings/{recordingId} puts a recording into a folder, DELETE/v1.0/directories/{directoryId}/folders/{folderId}/recordings/{recordingId} takes it out again. Both need read-write access to the folder and answer 204 without a body. Adding is idempotent, so calling it twice does no harm. If the recording does not exist in the company account, you get 404.

# Add
curl -X PUT "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders/$FOLDER_ID/recordings/$RECORDING_ID" \
-H "Authorization: Bearer $SALLY_TOKEN"

# Remove
curl -X DELETE "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders/$FOLDER_ID/recordings/$RECORDING_ID" \
-H "Authorization: Bearer $SALLY_TOKEN"

3. List the recordings of a folder​

GET/v1.0/directories/{directoryId}/folders/{folderId}/recordings returns the recordings of a folder, newest first. With includeSubfolders=true it also includes the recordings of all accessible subfolders, without duplicates.

ParameterTypeDefaultDescription
includeSubfoldersbooleanfalsetrue includes the recordings of all nested subfolders.
pageinteger1Page number, starting at 1.
pageSizeinteger25Entries per page, at most 100.
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders/$FOLDER_ID/recordings?includeSubfolders=true&pageSize=100" \
-H "Authorization: Bearer $SALLY_TOKEN"
200 OK
{
"page": 1,
"pageSize": 100,
"total": 3,
"hasMore": false,
"items": [
{
"recordingId": "53e2e041-4b34-41c7-a285-634d80b3675d",
"durationInSeconds": 1843,
"isManualUpload": false,
"appointmentId": "d4398f97-cd92-4d21-a6ec-4aab0a85441b",
"createdOn": "2026-09-24T09:30:00Z"
}
]
}
FieldTypeMeaning
recordingIdstringId of the recording. Fetch its details with GET …/recordings/{recordingId}.
durationInSecondsnumber or nullDuration in seconds, null if unknown.
isManualUploadbooleantrue if the recording was uploaded manually (no meeting bot).
appointmentIdstring or nullYour own appointment linked to the recording, null if none.
createdOnstringCreation timestamp (ISO 8601, UTC).

4. Rename or move a folder​

PATCH/v1.0/directories/{directoryId}/folders/{folderId} changes the name, the description or the place of a folder in the tree. Only the fields you send are changed, and the shares stay untouched. You need read-write access to the folder.

FieldTypeDescription
namestringNew folder name.
descriptionstring or nullNew description. null clears it.
parentFolderIdstring or nullMoves the folder under this parent. null makes it a top-level folder.
curl -X PATCH "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders/$FOLDER_ID" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"parentFolderId": "bf7cd0f0-3c69-4690-a956-9c30b123ab30"}'

The answer is the updated folder, in the same shape as the entries in Find folders.

5. Share folders​

Sharing happens on the folder, with individual users or with whole teams. That is the practical way to open a set of meetings to a department without touching every recording. PUT/v1.0/directories/{directoryId}/folders/{folderId}/shares sets the shares, and it replaces the complete list. Only the folder owner may change shares.

FieldTypeDescription
userSharesarrayRequired. Direct user shares, each {"userId": "…", "accessLevel": "…"}.
teamSharesarrayRequired. Team shares, each {"teamId": "…", "accessLevel": "…"}.

accessLevel is none, read or readWrite. To add one share without losing the others, read the current shares with GET/v1.0/directories/{directoryId}/folders/{folderId}/shares first, add yours and send both lists back.

This request gives a team read access and one user read-write access:

curl -X PUT "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/folders/$FOLDER_ID/shares" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userShares": [
{"userId": "11f967df-7dd1-4719-a9aa-46530940c443", "accessLevel": "readWrite"}
],
"teamShares": [
{"teamId": "3c35d421-a578-4ce7-aebb-c5add21907bd", "accessLevel": "read"}
]
}'

The answer holds the resulting shares, plus the shares the folder inherits from its parents:

200 OK
{
"userShares": [
{"userId": "11f967df-7dd1-4719-a9aa-46530940c443", "accessLevel": "readWrite"}
],
"teamShares": [
{"teamId": "3c35d421-a578-4ce7-aebb-c5add21907bd", "accessLevel": "read"}
],
"inheritedShares": [
{
"userId": "11f967df-7dd1-4719-a9aa-46530940c443",
"accessLevel": "read",
"inheritedFromFolderId": "a1ae243c-6bc2-4da4-a5d7-d70c50ba1acc"
}
]
}
Note

inheritedShares come from parent folders and are read-only, you cannot set them here. Shares carry only ids. Resolve users with the membership endpoints and teams with the team endpoints.

6. Tags​

Tags are labels on appointments and recordings. They are created once per company account in the tag catalog, carry a color from a fixed palette (for example blue or green) and are then assigned by id. Admins and owners manage the catalog.

GET/v1.0/directories/{directoryId}/tags returns all active tags of the catalog, most used first:

200 OK
{
"items": [
{"tagId": "18063a7e-a82b-4972-a850-f866b8b4a046", "name": "Customer", "colorCode": "indigo"}
]
}

POST/v1.0/directories/{directoryId}/tags adds a tag to the catalog. Names are deduplicated case-insensitively.

FieldTypeDescription
namestringRequired. Display name, at most 100 characters.
colorCodestring or nullA color name from the palette, not a hex value: indigo, purple, fuchsia, pink, rose, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, slate, gray, zinc, stone or neutral. An unknown key renders in the neutral default color. GET/v1.0/directories/{directoryId}/tags/colors lists the palette.

PUT/v1.0/directories/{directoryId}/recordings/{recordingId}/tags and PUT/v1.0/directories/{directoryId}/appointments/{appointmentId}/tags assign tags with a body of tagIds:

# Create a tag (admin or owner)
curl -X POST "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/tags" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Customer", "colorCode": "indigo"}'

# Assign it to a recording
curl -X PUT "https://api.sally.io/v1.0/directories/$DIRECTORY_ID/recordings/$RECORDING_ID/tags" \
-H "Authorization: Bearer $SALLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tagIds": ["18063a7e-a82b-4972-a850-f866b8b4a046"]}'

The assignment answers with the tags as an array:

200 OK
[
{"tagId": "18063a7e-a82b-4972-a850-f866b8b4a046", "name": "Customer", "colorCode": "indigo"}
]
Important

Assigning replaces the complete set of tags and only accepts ids of existing tags. Create a missing tag first, then assign it. A recording or an appointment can carry up to 20 tags of its own.

A recording can also inherit tags from its linked appointments and their recurring series. Those stay dynamic, the call on the recording only sets its direct tags. Series tags live on the recurring master and are not set on a single appointment. The tags field of a recording lists its effective tags, own and inherited, so build the set you assign from your own data rather than copying that field.

7. Complete script​

This script files a recording into a folder by name and gives it a set of tags by name. It finds the folder with a search, looks up each tag in the catalog case-insensitively, creates missing tags (this part needs an admin or owner token) and then assigns the whole set. Set RECORDING_ID, FOLDER_NAME and TAG_NAMES (comma-separated) before you run it.

#!/usr/bin/env bash
# Needs SALLY_TOKEN, DIRECTORY_ID, RECORDING_ID, FOLDER_NAME, TAG_NAMES, plus jq.
set -euo pipefail
BASE="https://api.sally.io/v1.0/directories/$DIRECTORY_ID"
AUTH="Authorization: Bearer $SALLY_TOKEN"

# 1. Find the folder by exact name (case-insensitive) and file the recording.
folder_id=$(curl -s -G "$BASE/folders" -H "$AUTH" --data-urlencode "search=$FOLDER_NAME" \
| jq -r --arg n "$FOLDER_NAME" '.items[] | select((.name | ascii_downcase) == ($n | ascii_downcase)) | .folderId' | head -n1)
[ -n "$folder_id" ] || { echo "Folder not found: $FOLDER_NAME" >&2; exit 1; }
curl -s -X PUT "$BASE/folders/$folder_id/recordings/$RECORDING_ID" -H "$AUTH"

# 2. Resolve tag names to ids, creating missing tags.
catalog=$(curl -s "$BASE/tags" -H "$AUTH")
tag_ids=()
IFS=',' read -ra names <<< "$TAG_NAMES"
for name in "${names[@]}"; do
id=$(echo "$catalog" | jq -r --arg n "$name" '.items[] | select((.name | ascii_downcase) == ($n | ascii_downcase)) | .tagId' | head -n1)
if [ -z "$id" ]; then
id=$(curl -s -X POST "$BASE/tags" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg n "$name" '{name: $n}')" | jq -r '.tagId')
fi
tag_ids+=("$id")
done

# 3. Assign the complete set.
curl -s -X PUT "$BASE/recordings/$RECORDING_ID/tags" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(printf '%s\n' "${tag_ids[@]}" | jq -R . | jq -s '{tagIds: .}')" | jq .

8. Endpoints at a glance​

Each entry leads to the reference, where you can send the request with your own token.

Folders

Tags