> For the complete documentation index, see [llms.txt](https://docs.beefree.io/beefree-sdk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.beefree.io/beefree-sdk/apis/content-services-api/file-manager.md).

# File Manager

This page lists and describes the File Manager category of endpoints within the Content Services API. It also includes interactive testing environments for each endpoint in this category.

{% hint style="info" %}
File Manager endpoints are part of the [Content Services API](/beefree-sdk/apis/content-services-api.md). The Content Services API is available on [Beefree SDK plans that are Essentials or above](https://developers.beefree.io/pricing-plans).
{% endhint %}

## Overview

The File Manager endpoints let your application manage an end user's files and folders from your own server, without opening a builder. They act on the same file system the File Manager displays inside the editor, so anything your application changes through the API your end users will see next time they open it.

A single resource family, `/v1/file`, covers six operations. The following table lists them.

| Operation                                                 | Request                                                          |
| --------------------------------------------------------- | ---------------------------------------------------------------- |
| [List a directory](#list-a-directory)                     | `GET /v1/file/{path}/`                                           |
| [Search files](#search-files)                             | `GET /v1/file/{path}?q={query}`                                  |
| [Create a directory](#create-a-directory)                 | `POST /v1/file/{path}/`                                          |
| [Upload a file](#upload-a-file)                           | `POST /v1/file/{path}/{filename}`                                |
| [Move a file](#move-a-file)                               | `PATCH /v1/file/{path}/{filename}`                               |
| [Delete a file or directory](#delete-a-file-or-directory) | `DELETE /v1/file/{path}/{filename}` or `DELETE /v1/file/{path}/` |

Everything after `/v1/file/` is the path to a file or directory inside your end user's storage, and a trailing slash decides which of the two a request addresses. Reference the [Paths and the trailing slash](#paths-and-the-trailing-slash) section before you write against these endpoints.

Each request counts as one Content Services API call, and these endpoints are subject to the [same rate limits](/beefree-sdk/apis/content-services-api.md#rate-limits) as every other category.

## Headers and authentication

Every request carries your Content Services API key as a bearer token, plus a header identifying the end user whose files you're working with.

```http
Authorization: Bearer {your API key}
X-BEE-Uid: {end-user id}
```

If your application already calls the Content Services API, your existing key works on these endpoints and there's nothing new to provision. Reference the [Authentication page](/beefree-sdk/apis/content-services-api/authentication.md) to learn more about creating and managing keys. A missing or invalid `Authorization` header returns `401`.

### Identify the end user

`X-BEE-Uid` is the same `uid` your application uses when it creates an editor session, so the API and the editor resolve to the same file system. It's required on every call, including a listing of the storage root. A `uid` that has never been used before works fine, since the value identifies a customer of yours rather than a Beefree SDK account, and we don't validate it.

If the header is missing or empty, the request fails with `400`:

```json
{
  "code": 3750,
  "message": "Request Error",
  "details": "Missing x-bee-uid header"
}
```

### Other headers

CDN segmentation, custom domains, and custom file system providers each require additional headers. Send the ones for each feature your application has configured. If you don't use any of them, `Authorization` and `X-BEE-Uid` are all you need. The following table lists the headers by feature.

| Feature                     | Headers                                                                               |
| --------------------------- | ------------------------------------------------------------------------------------- |
| CDN segmentation            | `X-BEE-TENANT-IDENTIFIER`                                                             |
| Custom file system provider | `X-BEE-BUSINESSUID`, `X-BEE-DOCUMENT-ID`, `X-BEE-CUSTOM-HOST`, `X-BEE-AUTHORIZATION`  |
| Custom domain               | `X-BEE-CUSTOM-THIRD-LEVEL-DOMAIN`, `X-BEE-CUSTOM-DOMAIN`, `X-BEE-CUSTOM-MEDIA-DOMAIN` |

`X-BEE-AUTHORIZATION` carries the credentials for your own file system provider, not for Beefree SDK. Your Content Services API key always goes in the `Authorization` header.

### These endpoints are server-to-server only

These endpoints accept a Content Services API key only. The access token your backend obtains from `/loginV2` to start a builder session doesn't work here, and sending one returns `403`:

```json
{
  "status": 403,
  "code": 403,
  "message": "Resource forbidden: API token required."
}
```

{% hint style="danger" %}
Keep secrets on the server. Never expose your Client Secret or Content Services API key to the browser. Calling these endpoints from a browser would expose your API key.
{% endhint %}

## Storage requirements

These endpoints require your application's file storage provider to be updated to the latest version. Requests from an application on an earlier version return `422`:

```json
{
  "code": 3621,
  "message": "Storage configuration error: feature available only on FSPx"
}
```

If you see this error, contact our team and we'll confirm which version your application is on.

Moving files can also be disabled for an individual storage. When it is, [Move a file](#move-a-file) returns `422` with code `3622`, and files come back from a listing with `extra.can-move` set to `false`.

## Paths and the trailing slash

Everything after `/v1/file/` is the path inside the end user's storage.

### The trailing slash chooses the operation

A trailing slash means directory, and no trailing slash means file. This single character changes what a request does. The following table shows the same route with and without it.

| Request                              | Result                                       |
| ------------------------------------ | -------------------------------------------- |
| `POST /v1/file/campaigns/`           | Creates the directory `campaigns`            |
| `POST /v1/file/campaigns/hero.jpg`   | Uploads the file `hero.jpg` into `campaigns` |
| `DELETE /v1/file/campaigns/`         | Deletes `campaigns` if it's empty            |
| `DELETE /v1/file/campaigns/hero.jpg` | Deletes the single file `hero.jpg`           |
| `PATCH /v1/file/campaigns/`          | Refused, because only files can be moved     |

On `GET` the trailing slash isn't enforced, so `GET /v1/file/campaigns` and `GET /v1/file/campaigns/` both work. The path is forwarded as given and normalized by the storage service.

### The storage root and reserved directories

What the root contains depends on whether your application has a [Customer Shared Folder](/beefree-sdk/server-side-configurations/server-side-options/storage-options/configure-your-aws-s3-bucket.md#shared-assets) configured.

With a Customer Shared Folder, the root holds two directories and nothing else. Your end user's own files live in `/myfiles`, where every operation is available. The shared files live in `/shared`, which supports listing and searching only; creating, uploading, moving, and deleting return an error. Neither directory can be deleted.

Without one, your end users work directly in the root, uploading files and creating directories there.

Either way, the root itself can't be created, moved, or deleted.

* `GET /v1/file` and `GET /v1/file/` both list the root.
* `POST /v1/file`, `PATCH /v1/file`, and `DELETE /v1/file` return `404`, because there's no such route.
* `POST /v1/file/`, `PATCH /v1/file/`, and `DELETE /v1/file/` return `400` with a `details` message explaining that a path is required.&#x20;

To upload to the root, use a single-segment path: `POST /v1/file/hero.jpg`.

### Valid paths

Path segments are validated and re-encoded before they're forwarded to storage.

Paths and file names can contain spaces, `&`, `%`, non-ASCII characters, and emoji. `/my folder/a&b.txt` and `/😀/x.png` are both valid, and a literal `%` stays a literal `%`.

Two things are rejected with `400` and `details` of `Invalid path`:

* Any `.` or `..` segment, including percent-encoded forms such as `%2e%2e%2f`.
* An empty inner segment, which means a leading `//` as in `//foo`, or a repeated slash as in `a//b`. A repeated slash matters because `a//b` addresses a different location in storage than `a/b`.&#x20;

Paths are case-sensitive, and so is the route itself. `/V1/File/...` returns `404`.

## Responses and errors

A successful call returns `200`, with the payload of the operation in `data`. For a listing or a search, `data` carries the items. For a write operation, it carries the metadata of what you wrote.

```json
{
  "status": "success",
  "data": { }
}
```

### Read code, not just the HTTP status

Three different error bodies can reach your client, so read `code` rather than relying on the status alone. Logical failures, such as a name collision or a missing source file, arrive as `400` with the storage error body preserved.

| code          | Raised by                                                                                          | Where the detail is                                      |
| ------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `3750`        | The API layer rejecting the request                                                                | `details`, a string. `message` is always `Request Error` |
| `3500`        | Field validation in the storage layer                                                              | `details`, an object with one key per invalid field      |
| Anything else | Storage or your file storage provider, such as `3400` for a collision or `3200` for a missing file | `message`. `details` may be present but empty            |

```json
{
  "code": 3750,
  "message": "Request Error",
  "details": "conflict_strategy is required and must be one of keep, replace, ask"
}
```

### HTTP status codes

The following table lists the statuses these endpoints return.

| Status | Meaning                                                                                                                                           |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | Success                                                                                                                                           |
| `400`  | The request was rejected with code `3750`, a field failed validation, or the operation failed logically and the body carries its own `code`       |
| `401`  | `Authorization` missing or invalid                                                                                                                |
| `403`  | An access token was used instead of an API key                                                                                                    |
| `404`  | No such route, which means a bare `/v1/file` on `POST`, `PATCH`, or `DELETE`, or a wrong-cased path                                               |
| `413`  | Upload body larger than the maximum upload size                                                                                                   |
| `422`  | Rejected by storage, such as `3621` when the file storage provider needs updating, `3622` when moving is disabled, or `4220` for a dangerous file |
| `429`  | Rate limit exceeded. The body is plain text, not the JSON envelope                                                                                |
| `500`  | Unexpected failure                                                                                                                                |

## List a directory <a href="#list-a-directory" id="list-a-directory"></a>

## GET /v1/file/{path}

> Use this endpoint to return the contents of a directory in an end user's File Manager storage. \`GET /v1/file\` lists the storage root. Adding a \`q\` parameter turns this same call into a search.\
> \
> \### How it works\
> \
> To list an end user's files:\
> \
> 1\. Set the path to the directory you want to read.\
> 2\. Send the request with your API key and the end user's \`X-BEE-Uid\`.\
> 3\. Read the directory contents from \`items\`, and the directory's own metadata from \`meta\`.\
> \
> \### Notes\
> \
> \- Listings return every item in the directory. There's no pagination.\
> \- Any query parameter other than \`q\`, including a lone \`mode\`, isn't forwarded.<br>

```json
{"openapi":"3.0.0","info":{"title":"List a directory","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}":{"get":{"description":"Use this endpoint to return the contents of a directory in an end user's File Manager storage. `GET /v1/file` lists the storage root. Adding a `q` parameter turns this same call into a search.\n\n### How it works\n\nTo list an end user's files:\n\n1. Set the path to the directory you want to read.\n2. Send the request with your API key and the end user's `X-BEE-Uid`.\n3. Read the directory contents from `items`, and the directory's own metadata from `meta`.\n\n### Notes\n\n- Listings return every item in the directory. There's no pagination.\n- Any query parameter other than `q`, including a lone `mode`, isn't forwarded.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The directory to list, inside the end user's storage. Use an empty path to list the storage root.","schema":{"type":"string"}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"success on a successful call"},"data":{"type":"object","properties":{"items":{"type":"array","description":"The contents of the directory","items":{"type":"object","properties":{"name":{"type":"string","description":"File or directory name"},"path":{"type":"string","description":"Full path inside the storage"},"last-modified":{"type":"integer","format":"int64","description":"Last modification time, in epoch milliseconds"},"permissions":{"type":"string","description":"Permissions on the item"},"extra":{"type":"object","properties":{"can-move":{"type":"boolean","description":"Whether the file can be moved on this storage"}}},"mime-type":{"type":"string","description":"MIME type. Directories use application/directory"},"size":{"type":"integer","description":"Size in bytes"},"public-url":{"type":"string","description":"Publicly reachable URL of the file"},"thumbnail":{"type":"string","description":"Thumbnail URL, when the storage generates one"}}}},"meta":{"type":"object","description":"Metadata for the directory itself","properties":{"name":{"type":"string"},"path":{"type":"string"},"last-modified":{"type":"integer","format":"int64"},"permissions":{"type":"string"},"mime-type":{"type":"string"},"size":{"type":"integer"},"item-count":{"type":"integer","description":"Number of items in the directory"},"item-count-details":{"type":"object","description":"The item count split by type","properties":{"dirs":{"type":"integer"},"files":{"type":"integer"}}}}}}}}}}}},"400":{"description":"Bad request. A storage-side failure, such as the directory not existing, is also returned here with the storage error body forwarded unchanged.","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Search files <a href="#search-files" id="search-files"></a>

## GET /v1/file/{path}

> Use this endpoint to search an end user's File Manager files by name. Search isn't a separate endpoint: adding \`q\` to a listing URL turns that call into a search, and the path in the URL is the scope. Use the bare root to search the whole storage, or a path to search that directory and its subtree.\
> \
> Search runs against Beefree SDK's own file system, so it isn't available on applications configured with custom storage. Those requests return \`422\` with code \`3621\`. The other five operations are unaffected.\
> \
> \### How it works\
> \
> To search an end user's files:\
> \
> 1\. Set the path to the directory you want to search, or use the bare root to search everything.\
> 2\. Pass the search term as \`q\`, and optionally a \`mode\` to change how names are matched.\
> 3\. Read the matches from \`items\`, and check \`truncated\` to see whether the result set was cut off.\
> \
> \### Matching modes\
> \
> \| Mode | Matches |\
> \| --- | --- |\
> \| \`exact\` | The file name matches the query |\
> \| \`prefix\` | The file name starts with the query. \`q=sum\` matches \`summer.jpg\` and \`summit-banner.png\` |\
> \| \`contains\` | The query appears anywhere in the file name. \`q=banner\` matches \`summer-banner.jpg\` and \`top-banner-2025.png\` |\
> \| \`fuzzy\` | File names a few characters different from the query, which makes it tolerant of typos |\
> \
> Fuzzy matching reliably tolerates one or two character differences per token, and up to three when they are spread across a multi-word query. Use \`prefix\` or \`contains\` when your application knows the naming convention it is looking for, since both give predictable results.\
> \
> \### Notes\
> \
> \- A search returns at most 200 matches. When more files match, \`truncated\` is \`true\`, which tells you the result set is incomplete rather than empty. There's no pagination, so narrow the query or search a deeper path to get a complete result.\
> \- All four modes compare the query against file names. Search doesn't read file contents, and it doesn't match on meaning, so a query of \`product photos\` won't return a file named \`hero-banner.jpg\` however closely related the two are.\
> \- \`path\` can't be passed as a query parameter, because the scope always comes from the URL. Sending \`?path=\` returns \`400\`. There's no reserved URL segment either, so a directory named \`search\` is an ordinary scope.<br>

```json
{"openapi":"3.0.0","info":{"title":"Search files","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}":{"get":{"description":"Use this endpoint to search an end user's File Manager files by name. Search isn't a separate endpoint: adding `q` to a listing URL turns that call into a search, and the path in the URL is the scope. Use the bare root to search the whole storage, or a path to search that directory and its subtree.\n\nSearch runs against Beefree SDK's own file system, so it isn't available on applications configured with custom storage. Those requests return `422` with code `3621`. The other five operations are unaffected.\n\n### How it works\n\nTo search an end user's files:\n\n1. Set the path to the directory you want to search, or use the bare root to search everything.\n2. Pass the search term as `q`, and optionally a `mode` to change how names are matched.\n3. Read the matches from `items`, and check `truncated` to see whether the result set was cut off.\n\n### Matching modes\n\n| Mode | Matches |\n| --- | --- |\n| `exact` | The file name matches the query |\n| `prefix` | The file name starts with the query. `q=sum` matches `summer.jpg` and `summit-banner.png` |\n| `contains` | The query appears anywhere in the file name. `q=banner` matches `summer-banner.jpg` and `top-banner-2025.png` |\n| `fuzzy` | File names a few characters different from the query, which makes it tolerant of typos |\n\nFuzzy matching reliably tolerates one or two character differences per token, and up to three when they are spread across a multi-word query. Use `prefix` or `contains` when your application knows the naming convention it is looking for, since both give predictable results.\n\n### Notes\n\n- A search returns at most 200 matches. When more files match, `truncated` is `true`, which tells you the result set is incomplete rather than empty. There's no pagination, so narrow the query or search a deeper path to get a complete result.\n- All four modes compare the query against file names. Search doesn't read file contents, and it doesn't match on meaning, so a query of `product photos` won't return a file named `hero-banner.jpg` however closely related the two are.\n- `path` can't be passed as a query parameter, because the scope always comes from the URL. Sending `?path=` returns `400`. There's no reserved URL segment either, so a directory named `search` is an ordinary scope.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The directory to search, inside the end user's storage. Use an empty path to search the whole storage.","schema":{"type":"string"}},{"name":"q","in":"query","required":true,"description":"The search term, minimum three characters. Its presence is what selects search. Must appear exactly once.","schema":{"type":"string"}},{"name":"mode","in":"query","required":false,"description":"How file names are matched. exact matches the whole name, prefix matches names starting with the query, contains matches the query anywhere in the name, and fuzzy tolerates typos. Must appear at most once.","schema":{"type":"string","enum":["exact","prefix","contains","fuzzy"],"default":"exact"}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"data":{"type":"object","properties":{"mode":{"type":"string","description":"The matching mode that was applied"},"path":{"type":"string","description":"The resolved search scope"},"truncated":{"type":"boolean","description":"true when the result set was cut off at 200 results"},"items":{"type":"array","description":"The matching files","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"},"mime-type":{"type":"string"},"size":{"type":"integer"},"last-modified":{"type":"integer","format":"int64"},"public-url":{"type":"string"},"thumbnail":{"type":"string"},"score":{"type":"number","format":"float","description":"How close the file name is to the query. Included for fuzzy searches only."}}}}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Create a directory <a href="#create-a-directory" id="create-a-directory"></a>

## POST /v1/file/{path}/

> Use this endpoint to create a directory in an end user's File Manager storage. The trailing slash is mandatory, because without it the same route uploads a file.\
> \
> \### How it works\
> \
> To create a folder in an end user's storage:\
> \
> 1\. Set the path to the directory you want to create, ending with a slash.\
> 2\. Send the request with no body and no query parameters.\
> 3\. Read the new directory's metadata from \`meta\`.\
> \
> \### Notes\
> \
> \- Creating \`a/b/c/\` when \`a/b/\` doesn't exist creates the whole chain.\
> \- The body must be empty. A body on a directory path is rejected, because forwarding it would make the storage create a directory where you meant to upload a file.\
> \- \`conflict\_strategy\` isn't accepted here.<br>

```json
{"openapi":"3.0.0","info":{"title":"Create a directory","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}/":{"post":{"description":"Use this endpoint to create a directory in an end user's File Manager storage. The trailing slash is mandatory, because without it the same route uploads a file.\n\n### How it works\n\nTo create a folder in an end user's storage:\n\n1. Set the path to the directory you want to create, ending with a slash.\n2. Send the request with no body and no query parameters.\n3. Read the new directory's metadata from `meta`.\n\n### Notes\n\n- Creating `a/b/c/` when `a/b/` doesn't exist creates the whole chain.\n- The body must be empty. A body on a directory path is rejected, because forwarding it would make the storage create a directory where you meant to upload a file.\n- `conflict_strategy` isn't accepted here.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The directory to create, inside the end user's storage.","schema":{"type":"string"}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"success on a successful call"},"data":{"type":"object","properties":{"meta":{"type":"object","description":"Metadata for the directory","properties":{"name":{"type":"string","description":"Directory name"},"path":{"type":"string","description":"Full path inside the storage"},"last-modified":{"type":"integer","format":"int64","description":"Last modification time, in epoch milliseconds"},"permissions":{"type":"string","description":"Permissions on the directory"},"extra":{"type":"object","description":"Additional storage-side flags"},"mime-type":{"type":"string","description":"MIME type. Directories use application/directory"},"size":{"type":"integer","description":"Size in bytes"},"item-count":{"type":"integer","description":"Number of items in the directory"},"item-count-details":{"type":"object","description":"The item count split by type","properties":{"dirs":{"type":"integer","description":"Number of directories"},"files":{"type":"integer","description":"Number of files"}}}}}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"404":{"description":"No such route, which means POST /v1/file with no path at all. No response body"},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Upload a file

## POST /v1/file/{path}/{filename}

> Use this endpoint to upload a file to an end user's File Manager storage. The last path segment is the file name and everything before it is the destination directory, so there must be no trailing slash. To upload to the storage root, use a single-segment path: \`POST /v1/file/{filename}\`.\
> \
> The stored name always comes from the URL. File names may contain spaces, \`&\`, \`%\`, and emoji.\
> \
> \### How it works\
> \
> To upload a file to an end user's storage:\
> \
> 1\. Set the path to the destination directory and file name, with no trailing slash.\
> 2\. Choose a \`conflict\_strategy\` and add it to the query string.\
> 3\. Send the file as raw bytes, as multipart form data, or as a URL for Beefree SDK to fetch.\
> 4\. Read the stored file's metadata, including its \`public-url\`, from \`meta\`.\
> \
> \### Choose what happens on a collision\
> \
> \`conflict\_strategy\` is mandatory, so collision behavior is always an explicit choice rather than a default you inherit.\
> \
> \| Value | Behavior |\
> \| --- | --- |\
> \| \`keep\` | The existing file is preserved and the new one is numbered. If \`a.png\` exists, the upload is stored as \`a\_1.png\` |\
> \| \`replace\` | The existing file is overwritten |\
> \| \`ask\` | The call fails with \`400\` and \`{"code": 3400, "message": "Resource Already Present"}\` |\
> \
> \### Three ways to send the file\
> \
> The \`Content-Type\` header selects the upload mode.\
> \
> 1\. \*\*Raw bytes.\*\* Set \`Content-Type\` to the file's own MIME type, such as \`image/jpeg\` or \`application/pdf\`, and send the bytes as the body. The \`Content-Type\` has to be a well-formed \`type/subtype\`.\
> 2\. \*\*Multipart form data.\*\* Send a normal \`multipart/form-data\` body. A \`boundary\` parameter is mandatory, and without it the request is rejected. The part name and any \`filename\` inside the multipart body are ignored, since the stored name comes from the URL.\
> 3\. \*\*From a URL.\*\* Set \`Content-Type: application/json\` and send \`{"url": "<https://..."}`>. The file is fetched server-side, so no bytes travel through the API. The URL has to be an absolute \`http\` or \`https\` URL, can't contain a comma, and can be at most 20,000 characters.\
> \
> The URL mode is the one to reach for when an AI agent or an external service has already produced a file and given you a link to it.\
> \
> \### Notes\
> \
> \- The maximum upload size is 20 MB by default, and it applies to all three upload modes. A larger body returns \`413\`.\
> \- The response carries the file's \`public-url\`, so an application that captures it here doesn't need a follow-up search to find the file it just uploaded.\
> \- The body is validated before \`conflict\_strategy\`, so a malformed upload is told about its actual problem first.<br>

```json
{"openapi":"3.0.0","info":{"title":"Upload a file","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}/{filename}":{"post":{"description":"Use this endpoint to upload a file to an end user's File Manager storage. The last path segment is the file name and everything before it is the destination directory, so there must be no trailing slash. To upload to the storage root, use a single-segment path: `POST /v1/file/{filename}`.\n\nThe stored name always comes from the URL. File names may contain spaces, `&`, `%`, and emoji.\n\n### How it works\n\nTo upload a file to an end user's storage:\n\n1. Set the path to the destination directory and file name, with no trailing slash.\n2. Choose a `conflict_strategy` and add it to the query string.\n3. Send the file as raw bytes, as multipart form data, or as a URL for Beefree SDK to fetch.\n4. Read the stored file's metadata, including its `public-url`, from `meta`.\n\n### Choose what happens on a collision\n\n`conflict_strategy` is mandatory, so collision behavior is always an explicit choice rather than a default you inherit.\n\n| Value | Behavior |\n| --- | --- |\n| `keep` | The existing file is preserved and the new one is numbered. If `a.png` exists, the upload is stored as `a_1.png` |\n| `replace` | The existing file is overwritten |\n| `ask` | The call fails with `400` and `{\"code\": 3400, \"message\": \"Resource Already Present\"}` |\n\n### Three ways to send the file\n\nThe `Content-Type` header selects the upload mode.\n\n1. **Raw bytes.** Set `Content-Type` to the file's own MIME type, such as `image/jpeg` or `application/pdf`, and send the bytes as the body. The `Content-Type` has to be a well-formed `type/subtype`.\n2. **Multipart form data.** Send a normal `multipart/form-data` body. A `boundary` parameter is mandatory, and without it the request is rejected. The part name and any `filename` inside the multipart body are ignored, since the stored name comes from the URL.\n3. **From a URL.** Set `Content-Type: application/json` and send `{\"url\": \"https://...\"}`. The file is fetched server-side, so no bytes travel through the API. The URL has to be an absolute `http` or `https` URL, can't contain a comma, and can be at most 20,000 characters.\n\nThe URL mode is the one to reach for when an AI agent or an external service has already produced a file and given you a link to it.\n\n### Notes\n\n- The maximum upload size is 20 MB by default, and it applies to all three upload modes. A larger body returns `413`.\n- The response carries the file's `public-url`, so an application that captures it here doesn't need a follow-up search to find the file it just uploaded.\n- The body is validated before `conflict_strategy`, so a malformed upload is told about its actual problem first.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The destination directory, inside the end user's storage.","schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"description":"The name the file is stored under. Always taken from the URL, never from the request body. May contain spaces, ampersands, percent signs, and emoji.","schema":{"type":"string"}},{"name":"conflict_strategy","in":"query","required":true,"description":"What to do when a file of that name already exists. keep preserves the existing file and numbers the new one, so a.png becomes a_1.png. replace overwrites the existing file. ask fails with Resource Already Present. Must appear exactly once.","schema":{"type":"string","enum":["keep","replace","ask"]}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The file, sent in one of three forms.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Absolute http or https URL for Beefree SDK to fetch the file from. Cannot contain a comma, and can be at most 20,000 characters."}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file bytes. A boundary parameter is mandatory. The part name and any filename inside the body are ignored."}}}},"application/octet-stream":{"schema":{"type":"string","format":"binary","description":"Raw file bytes, with Content-Type set to the file's own MIME type, such as image/jpeg or application/pdf."}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"success on a successful call"},"data":{"type":"object","properties":{"meta":{"type":"object","description":"Metadata for the stored file","properties":{"name":{"type":"string","description":"File name, as stored"},"path":{"type":"string","description":"Full path inside the storage"},"last-modified":{"type":"integer","format":"int64","description":"Last modification time, in epoch milliseconds"},"permissions":{"type":"string","description":"Permissions on the item"},"extra":{"type":"object","description":"Additional storage-side flags","properties":{"can-move":{"type":"boolean","description":"Whether the file can be moved on this storage"}}},"mime-type":{"type":"string","description":"MIME type"},"size":{"type":"integer","description":"Size in bytes"},"public-url":{"type":"string","description":"Publicly reachable URL of the file"},"thumbnail":{"type":"string","description":"Thumbnail URL, when the storage generates one"}}}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"413":{"description":"Payload too large","content":{"application/json":{}}},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Move a file

## PATCH /v1/file/{path}/{filename}

> Use this endpoint to move a file into a different directory in an end user's File Manager storage, keeping its file name.\
> \
> This endpoint moves files only, and it doesn't rename them. A path ending in \`/\` is refused, because directories can't be moved. \`new\_directory\` is always a destination directory rather than a target file name, so there's no way to rename a file in the same call.\
> \
> \### How it works\
> \
> To move a file:\
> \
> 1\. Set the path to the file you want to move.\
> 2\. Choose a \`conflict\_strategy\` and add it to the query string.\
> 3\. Send a JSON body naming the destination directory.\
> 4\. Read the file's new metadata from \`meta\`.\
> \
> \### Notes\
> \
> \- \`conflict\_strategy\` belongs in the query string. Sending it inside the JSON body returns \`400\` with \`details\` of \`Unexpected field in the request body: conflict\_strategy\`. It takes the same \`keep\`, \`replace\`, and \`ask\` values as an upload.\
> \- \`new\_directory\` accepts optional leading and trailing slashes, which are stripped, and surrounding whitespace is trimmed. A value of \`/\` means the storage root.\
> \- If the destination directory doesn't exist, it is created.\
> \- No field other than \`new\_directory\` is accepted, and an unexpected field returns \`400\` naming it.\
> \- Unlike the URL path, \`new\_directory\` isn't pre-decoded, so a literal \`%\` has to be sent as \`%\` rather than \`%25\`.<br>

```json
{"openapi":"3.0.0","info":{"title":"Move a file","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}/{filename}":{"patch":{"description":"Use this endpoint to move a file into a different directory in an end user's File Manager storage, keeping its file name.\n\nThis endpoint moves files only, and it doesn't rename them. A path ending in `/` is refused, because directories can't be moved. `new_directory` is always a destination directory rather than a target file name, so there's no way to rename a file in the same call.\n\n### How it works\n\nTo move a file:\n\n1. Set the path to the file you want to move.\n2. Choose a `conflict_strategy` and add it to the query string.\n3. Send a JSON body naming the destination directory.\n4. Read the file's new metadata from `meta`.\n\n### Notes\n\n- `conflict_strategy` belongs in the query string. Sending it inside the JSON body returns `400` with `details` of `Unexpected field in the request body: conflict_strategy`. It takes the same `keep`, `replace`, and `ask` values as an upload.\n- `new_directory` accepts optional leading and trailing slashes, which are stripped, and surrounding whitespace is trimmed. A value of `/` means the storage root.\n- If the destination directory doesn't exist, it is created.\n- No field other than `new_directory` is accepted, and an unexpected field returns `400` naming it.\n- Unlike the URL path, `new_directory` isn't pre-decoded, so a literal `%` has to be sent as `%` rather than `%25`.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The directory the file is currently in.","schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"description":"The file to move. The file keeps this name in its new directory.","schema":{"type":"string"}},{"name":"conflict_strategy","in":"query","required":true,"description":"What to do when a file of that name already exists in the destination. Accepted values are keep, replace, and ask. Must appear exactly once, in the query string. Sending it in the request body returns 400.","schema":{"type":"string","enum":["keep","replace","ask"]}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Request body for the endpoint","content":{"application/json":{"schema":{"type":"object","required":["new_directory"],"properties":{"new_directory":{"type":"string","description":"The destination directory, non-empty. Leading and trailing slashes are optional and stripped, and surrounding whitespace is trimmed. A value of / means the storage root. No other field is accepted."}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"success on a successful call"},"data":{"type":"object","properties":{"meta":{"type":"object","description":"Metadata for the stored file","properties":{"name":{"type":"string","description":"File name, as stored"},"path":{"type":"string","description":"Full path inside the storage"},"last-modified":{"type":"integer","format":"int64","description":"Last modification time, in epoch milliseconds"},"permissions":{"type":"string","description":"Permissions on the item"},"extra":{"type":"object","description":"Additional storage-side flags","properties":{"can-move":{"type":"boolean","description":"Whether the file can be moved on this storage"}}},"mime-type":{"type":"string","description":"MIME type"},"size":{"type":"integer","description":"Size in bytes"},"public-url":{"type":"string","description":"Publicly reachable URL of the file"},"thumbnail":{"type":"string","description":"Thumbnail URL, when the storage generates one"}}}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"404":{"description":"No such route, which means PATCH /v1/file with no path at all. No response body"},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Delete a file or directory

## DELETE /v1/file/{path}/{filename}

> Use this endpoint to delete the file or directory at the path.\
> \
> A directory is deleted only when it is empty, so \`DELETE /v1/file/campaigns/\` removes \`campaigns\` only if it contains no files. To clear a folder, delete its files first and then the folder.\
> \
> \### How it works\
> \
> To delete a file or folder:\
> \
> 1\. Set the path to the file, or to the directory with a trailing slash.\
> 2\. Send the request with no body and no query parameters.\
> 3\. Check for a \`200\` response, which returns \`null\` in \`data\`.\
> \
> \### Notes\
> \
> \- Address a directory with a trailing slash and a file without one.\
> \- The storage root can't be deleted.<br>

```json
{"openapi":"3.0.0","info":{"title":"Delete a file or directory","version":"1.0.0"},"servers":[{"url":"https://api.getbee.io","description":"Base URL for the API"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Enter Developer Console API Key as Bearer token"}}},"paths":{"/v1/file/{path}/{filename}":{"delete":{"description":"Use this endpoint to delete the file or directory at the path.\n\nA directory is deleted only when it is empty, so `DELETE /v1/file/campaigns/` removes `campaigns` only if it contains no files. To clear a folder, delete its files first and then the folder.\n\n### How it works\n\nTo delete a file or folder:\n\n1. Set the path to the file, or to the directory with a trailing slash.\n2. Send the request with no body and no query parameters.\n3. Check for a `200` response, which returns `null` in `data`.\n\n### Notes\n\n- Address a directory with a trailing slash and a file without one.\n- The storage root can't be deleted.\n","parameters":[{"name":"path","in":"path","required":true,"description":"The directory the file is in, inside the end user's storage.","schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"description":"The file to delete. To delete an empty directory instead, end the path with a trailing slash and omit the file name.","schema":{"type":"string"}},{"name":"X-BEE-Uid","in":"header","required":true,"description":"The end user whose file system the request applies to. This is the same uid your application uses when it creates an editor session.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"data":{"nullable":true,"description":"null on a successful delete"}}}}}},"400":{"description":"Bad request","content":{"application/json":{}}},"401":{"description":"Authorization header missing or invalid"},"403":{"description":"An access token was used instead of a Content Services API key"},"404":{"description":"No such route, which means DELETE /v1/file with no path at all. No response body"},"422":{"description":"Rejected by storage","content":{"application/json":{}}},"429":{"description":"Rate limit exceeded. The body is plain text, not the JSON envelope"},"500":{"description":"Internal Server Error"}}}}}}
```

## Limitations

Consider the following when working with the File Manager endpoints:

* Every operation requires your application's file storage provider to be updated to the latest version.
* Directories can't be moved, and files can't be renamed.
* A directory is deleted only when it's empty.
* Search matches file names, not file contents or meaning, and returns at most 200 results with no pagination.
* Search isn't available on applications configured with custom storage.
* Listings return every item in a directory, with no pagination.
* The default maximum upload size is 20 MB.
* Creating, uploading, moving, and deleting are unavailable in the Customer Shared Folder (`/shared`).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.beefree.io/beefree-sdk/apis/content-services-api/file-manager.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
