FileNest/Docs

Folders API

Folders let you organise files into a tree structure within a project. They use a materialized path model — the full path from root (e.g. /invoices/2026/q1) is stored on creation and never recalculated, making hierarchy queries fast.

Create a folder

POST /v1/projects/{project_id}/folders

Required scope: folders:write

{
  "name": "invoices",
  "parent_folder_id": null
}

name must not contain /. To create a nested folder, supply the parent's id in parent_folder_id.

Response 201:

{
  "id": "fld_01j...",
  "organization_id": "org_01j...",
  "project_id": "prj_01j...",
  "parent_folder_id": null,
  "name": "invoices",
  "path": "/invoices",
  "created_at": "2026-06-21T10:00:00Z"
}

Ensure a path (idempotent)

POST /v1/projects/{project_id}/folders/ensure-path

Required scope: folders:write

Idempotently creates every missing segment of a path and returns the leaf folder. If the full path already exists, the existing folder is returned unchanged. This is the recommended way to create per-user or per-tenant folder hierarchies.

{
  "path": "users/alice/uploads"
}

Response 200: returns the leaf folder record.

{
  "id": "fld_03j...",
  "name": "uploads",
  "path": "/users/alice/uploads",
  "created_at": "2026-06-21T10:05:00Z"
}

Use this instead of create

ensure-path is idempotent and safe to call on every request. Use it when you need to guarantee a folder exists before creating an upload token — no need to check first.

List folders

GET /v1/projects/{project_id}/folders

Required scope: folders:read

Query parameters:

ParameterTypeDescription
namestringOptional exact name filter

Returns all non-deleted folders in the project ordered by path (breadth-first). Use the path field to reconstruct the hierarchy client-side — no recursive queries needed.

Response 200:

{
  "items": [
    {
      "id": "fld_01j...",
      "parent_folder_id": null,
      "name": "invoices",
      "path": "/invoices",
      "created_at": "2026-06-21T10:00:00Z"
    },
    {
      "id": "fld_02j...",
      "parent_folder_id": "fld_01j...",
      "name": "2026",
      "path": "/invoices/2026",
      "created_at": "2026-06-21T10:01:00Z"
    }
  ],
  "total": 2
}

Get a folder by path

GET /v1/projects/{project_id}/folders/by-path?path={path}

Required scope: folders:read

Resolves a slash-separated path string to the matching folder record. Returns 404 if the path does not exist.

Query parameters:

ParameterTypeDescription
pathstringSlash-separated path, e.g. users/alice/uploads

Response 200: returns the folder record.

Response 404: path does not exist — use ensure-path to create it.

Get a folder by ID

GET /v1/projects/{project_id}/folders/{folder_id}

Required scope: folders:read

Response 200: returns the folder record.

List files in a folder

GET /v1/projects/{project_id}/folders/{folder_id}/files

Required scope: folders:read

Returns files that are directly inside the specified folder. Accepts the same query parameters as List files (limit, offset, cursor, q, tags, status, category, etc.).

Move a file to a folder

POST /v1/projects/{project_id}/files/{file_id}/move

Required scope: folders:write

{
  "folder_id": "fld_01j..."
}

Set folder_id to null to move the file back to the project root.

Response 200: returns the updated file object.

Delete a folder

DELETE /v1/projects/{project_id}/folders/{folder_id}

Required scope: folders:write

Soft-deletes the folder by setting deleted_at. The delete is rejected with 409 Conflict if the folder contains any files or subfolders — move or delete them first.

Response 204: no content on success.

Response 409:

{
  "error": {
    "code": "CONFLICT",
    "message": "Folder is not empty"
  }
}

Common pattern — per-user storage

The recommended pattern for SaaS apps where each end-user has their own isolated folder:

// Server-side: called when the user logs in or before issuing an upload token
const folder = await fn.folders.ensurePath(`users/${session.userId}/uploads`);
 
const token = await fn.uploadTokens.create({
  folderId: folder.id,
  ownerUserId: session.userId,
  expiresIn: 3600,
});

Every file uploaded with this token is automatically placed in the user's folder and stamped with their owner_user_id.


Error codes

StatusCodeMeaning
404NOT_FOUNDFolder does not exist or is deleted
409CONFLICTFolder has files or subfolders
422VALIDATION_ERRORname contains a / character or path segments are invalid