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 tree
Section titled “The tree”- The root exists in every application without being created. Its id is
root, its path/, androotis 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
pathsuch 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.
Folder routes
Section titled “Folder routes”| 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.
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.
The file record
Section titled “The file record”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.
States
Section titled “States”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 |
Metadata and external references
Section titled “Metadata and external references”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).
Moving, copying and the trash
Section titled “Moving, copying and the trash”| 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.