---
title: "Files"
description: "List, read, download, copy, rename, move, delete and restore files and folders in a workspace library."
---

> Documentation Index
> Fetch the complete documentation index at: https://tarkle.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Files

Files and folders live in the workspace library. A file id is digits. A folder id is a UUID. Deleted items, hidden system folders and items of other workspaces answer `404 not_found`.

| Method and path | Scope | What it does |
| --- | --- | --- |
| `GET /files` | `files:read` | List the files and folders directly inside one folder |
| `GET /files/{id}` | `files:read` | Read one file or folder, with its `path` |
| `GET /files/by-path` | `files:read` | Read one item by path, such as `/photos/2026/a.jpg` |
| `GET /files/{id}/download` | `files:read` | Get a short-lived download link |
| `POST /files/{id}/copy` | `files:write` | Copy a file inside storage |
| `PATCH /files/{id}` | `files:write` | Rename, move or set the modification time |
| `DELETE /files/{id}` | `files:write` | Move to the bin |
| `POST /files/{id}/restore` | `files:write` | Bring an item back from the bin |

## List a folder

`GET /files` takes `folder` (a folder id) or `path` (names such as `/photos/2026`), not both. With neither it lists the root. Folders come first, then files, each ordered by id. `limit` is 1 to 1000 (default 100). Pass `next_cursor` back as `cursor`.

```bash
curl -H "Authorization: Bearer $TARKLE_TOKEN" -H "X-Workspace-Key: $KEY" \
  "https://tarkle.com/api/v1/files?path=/photos/2026&limit=50"
```

An entry has `id`, `type` (`file` or `folder`), `name`, `size` (files only), `category`, `parent_id`, `created_at`, `updated_at`, `modified_at`, `sha256` and `sha256_source` (files only), `etag`, `protected` and `protection_kind`.

Two folders with the same name make a path ambiguous: `409 ambiguous_path`. Use the id then.

### List a whole tree

Add `recursive=true` and `type=file` or `type=folder` (type is required) to get every descendant of that type as one flat list, each with an absolute `path`. A sync tool can read a tree in a few calls. Hidden system folders are skipped. A folder with more than 5,000 folders below it answers `413 too_large`.

## Download

```json
{ "download_url": "https://...", "expires_in": 900 }
```

Fetch the bytes from `download_url` soon. Files only.

## Copy

`POST /files/{id}/copy` with `{ "parent_id": "...", "name": "copy.pdf" }`. `parent_id` `null` is the root and omitted is the same folder. The name defaults to the same name. A taken name is `409 name_taken` (a copy never renames silently). Plan limits apply: `413 storage_full` or `413 file_too_large`. Folders answer `400 not_supported`.

## Rename and move

`PATCH /files/{id}` takes any of `name`, `parent_id` (a folder id or `null` for the root) and `modified_at` (files only), and at least one. Bytes never move. A taken name is `409 name_taken`. A folder into itself or its own subfolder is `400 invalid_destination`. System, team and active-request folders answer `403 protected`.

## Delete and restore

`DELETE /files/{id}` moves the item to the bin and answers `{ deleted, deleted_transfers }`. If an active transfer holds the item the answer is `409 transfer_active`. Repeat with `?confirm_delete_transfers=true` to delete those transfers too. A Feedback board, a paid transfer with purchases, a protected folder, a file directly inside a team folder and anything inside File requests answer `403 protected`.

`POST /files/{id}/restore` answers `410 expired` past the plan's bin retention, `409 not_deleted` for an item that is not in the bin and `409 parent_unavailable` when its folder is in the bin.

Every write is checked as the person the token acts as and is recorded in the workspace Activity with `via: api`.

Source: https://tarkle.com/docs/api/files/index.mdx
