Skip to content

Folders and files

Every application has one folder tree. Files live in folders; folders carry permissions that their contents inherit. This page explains the tree, the file record and its lifecycle. It is for backend developers and evaluators.

  • The root exists in every application without being created. Its id is root, its path /, and root is accepted wherever a folder id is (parent_id, folder_id). It cannot be renamed, moved, deleted or shared.
  • A name is at most 255 characters, without / or control characters, and not . or .. (invalid_name). Siblings may not share a name, ignoring case (name_conflict).
  • Folders nest at most 32 deep (depth_exceeded). A folder cannot move into its own subtree (invalid_move).
  • Every folder and file has a path such as /Marketing/2026/brochure.pdf. Paths change on a rename or a move; ids never do.

A listing (GET P/folders/:folder_id/children) answers folders first when sorted by name, pages with ?after=&limit= (default 100, at most 500), and shows only the children the caller may view. GET P/folders/:folder_id/path answers the breadcrumb from the root; a segment the caller may not view has name: null.

Route Does
GET P/folders/root, GET P/folders/:folder_id Read the root or one folder
POST P/folders Create a folder (parent_id, name, optional description, inherit, metadata, external_ref, and for a machine client owner_id and managed)
GET P/folders/by-external-ref/:external_ref Find folders by your own id
PATCH P/folders/:folder_id Rename, change the description or metadata, move with parent_id, or (a machine client) mark or unmark it with managed
POST P/folders/:folder_id/copy Copy the subtree: answers 202 with the new folder and a job_id
DELETE P/folders/:folder_id, POST P/folders/:folder_id/restore Trash the folder with its subtree, or bring it back
GET P/folders/:folder_id/usage Bytes, files and folders under it
POST P/folders/:folder_id/release-management The tenant owner’s emergency release of an application-managed folder, with a reason
GET P/jobs/:job_id The state of a 202’s job: queued, running, done or failed

Trashing a folder needs delete on it and on everything inside. When some descendants block it, the answer is 403 partial_permission with blocked, the first 100 ids. An empty folder is trashed at once (204); trashing a folder with content, restoring a folder and copying one answer 202 with a job_id. A move changes up to 1,000 descendants in the request; a larger one answers 202 too. While a job moves or copies a subtree, the folder answers folder_busy to other changes. A restored folder goes back into its original parent, or into the root when that one is gone.

folders.ts
import type { Folder } from '@central-code-station/ccs-files-client';
import { files } from './files-client';
// One folder per team, found again by your own id: no folder id to store on your side.
export async function teamFolder(teamId: string, teamName: string): Promise<Folder> {
const ref = `team:${teamId}`;
const { items } = await files.folders.listByExternalRef(ref);
if (items.length > 0) return items[0];
return files.folders.create({ parent_id: 'root', name: teamName, external_ref: ref, inherit: false });
}
// Everything you uploaded for a post, without a database query of your own.
export async function filesOfPost(postId: string) {
const { items } = await files.files.listByExternalRef(`post:${postId}`);
return items.filter((file) => file.state === 'available');
}

A folder your backend marks managed is an application-managed folder: it and everything below it are read-only for people, so a change in the Files console cannot break the references your application keeps. Every folder and file says so in managed and managed_by. See Application-managed folders.

A file is the record GET P/files/:file_id answers. The fields you use most:

Field Meaning
file_id The id. Never changes, never reused
folder_id, path, name, extension Where it is and what it is called. The extension follows the detected type
mime_type, kind, size, content_sha256 What the bytes are. kind is one of image, video, audio, pdf, document, spreadsheet, presentation, text, archive, other
title, description, category, department, tags, custom Your metadata. custom holds up to 50 keys of your own
external_ref Your own id for the file, a list and search filter
owner_id, created_by Who it belongs to and who uploaded it (user_id, client_id)
state pending_upload, available, quarantined or trashed
status The approval status: draft, in_review, approved, rejected or archived
visibility, public_url private, application or public; the URL of a public file, which does not expire but changes with a new current version or a move to another storage (Where files are stored)
hold null, or the file’s legal hold: { reason, placed_by, placed_at, until }
managed, managed_by Whether an application manages the file’s folder, and { client_id, name, managed_folder_id, managed_at } or null; folders carry them too. See Application-managed folders
current_version_no, versions_count See Versions
revision Grows with every change; send it as If-Match
media What processing read: width, height, duration, page count
processing, renditions The state of each processing step and each rendition, see Processing
inherit, entries_count Whether it inherits its folder’s entries, and how many entries of its own it has
my_actions What the caller may do with it. It does not reflect managed: people cannot change a managed file whatever it lists

lock and editable exist for check-out and editing, which are coming; today they are always null and false.

state Means Visible in listings and search
pending_upload The ticket exists, the bytes are not complete. Minting a URL answers file_pending No
available Uploaded. Processing may still be running Yes
quarantined Malware scanning found it infected: it is never served (file_quarantined) No
trashed In the trash: restorable, see below. A file processing refused is trashed by system No

Set metadata on the ticket or later with PATCH P/files/:file_id. custom merges: a key with null removes it. Tags are lower-cased and de-duplicated. When the application enforces a list of categories or departments, only listed values are accepted (taxonomy_value_invalid). Limits are on Limits.

external_ref is your own id, for example post:123. GET P/files/by-external-ref/:external_ref lists the files that carry it, and search takes it as an exact filter. It is not unique: two posts may share a file, a post may have many files. Products of the platform use the same field: CCS Chat marks its folders and files with chat:conversation:<conversation_id> (Media in Chat).

Route Does
PATCH P/files/:file_id with folder_id Move. Needs edit on the file and on the target folder
POST P/files/:file_id/copy A new file with a new id: the current version only, no entries, owned by the caller
DELETE P/files/:file_id Move to the trash
POST P/files/:file_id/restore Back from the trash; into another folder_id when the original folder is gone
POST P/files/:file_id/purge Delete for good, at once. Needs delete and files.manage, or the owner when the application allows it
GET P/trash, POST P/trash/empty The trashed items the caller may delete; empty the trash (files.manage)

The trash keeps items for the application’s trash_retention_days (default 30 days), then purges them and releases the quota. See Quota and retention.