A booster panel can keep files: documents its users upload, invoices and exports that a workflow generates, product images. The endpoints on this page let your server work with those files with an API key: upload a generated report, list what visitors sent, get a download link, clean up.
The endpoints are under /api/v3/booster/storage/private/. Panels themselves reach their
files through a separate access path that is not covered here.
Concepts
Folders and paths
Files are addressed by a path: a top-level folder, optional sub-folders and the
file name, separated by /, for example invoices/2026/INV-0042.pdf.
- The folder name may contain letters, digits,
_and-. - Sub-folder and file names may contain any characters except control characters and
/ \ : * ? " < > | # % '; they cannot be.or..or start or end with a space. - A path has at least two parts (
<folder>/<name>), at most 8 levels and 512 characters. - Folders and sub-folders are not created separately: they exist as long as they hold a file. A path is unique within a panel.
File storage settings
The panel owner turns the file storage on and defines folders in the panel's module
settings in the app. For each folder (or * for all others) the settings say who may
upload, read, list, change and delete files, which file types it accepts, how large a
file may be, how many files it may hold and whether files are deleted after a number of
days. A folder may give permanent public links (for images shown on a public page).
Who can access a panel's files
Requests act as the user who owns the API key. That user must own the panel (be its creator, or a member of the group that owns it) or have the panel shared with them, directly or through a group. The API key must carry the matching scope:
| Scope | Endpoints |
|---|---|
ayeto.booster.storage |
list, count, get, url, usage |
ayeto.booster.storage.write |
upload, upload-b64, update, delete, delete-many |
The write scope does not include the read scope; a key that reads and writes needs both. The endpoints work also while the panel's file storage is switched off, so you can still read and clean up stored files.
For requests on this page the folder settings apply like this:
- An operation set to none for a folder is disabled for everyone, including this API (
403). - Every other level allows API requests, on all files of the folder.
- A folder without settings (and without a
*entry) is accessible only to the API and the owner. - The accepted file types, the file size limit and the limits of files and data per folder do apply to uploads through this API.
Files
| Field | Type | Description |
|---|---|---|
id |
UUID | Identifier of the file. |
path |
string | Full path, e.g. invoices/2026/INV-0042.pdf. |
folder |
string | Top-level folder. |
name |
string | File name (last part of the path). |
size |
integer | Size in bytes. |
content_type |
string | Type of the content. Known formats (PNG, JPEG, GIF, WebP, AVIF, PDF, common audio and video) are recognised from the content and win over the declared type; HTML, SVG and XML are recognised too. |
sha256 |
string | SHA-256 of the content (hex). |
metadata |
object | Your data about the file (JSON object, at most 4 KB, keys cannot start with $ or contain .). |
record |
object or null |
The booster database record the file belongs to: {collection, key}. |
owner |
UUID or null |
The panel user who uploaded the file; null for files uploaded through the API. |
created, updated |
integer | Upload and last change time in milliseconds since the epoch. |
expires_at |
string or null |
When the file is deleted, in folders that keep files for a number of days. |
url |
string or null |
Link that shows or downloads the file (see Download links). |
download_url |
string or null |
Link that always downloads the file. |
Files and database records
A file can belong to a record of the panel's database: send the
record's collection and key with the upload or set it later with update. The record must
exist in the panel's database. By default a file is deleted when its record is deleted; the
folder settings can keep it. Store the file's id (or path) in the record value when
your panel needs to find the file from the record.
Download links
Links are absolute URLs under /api/v3/booster/storage/file/ and need no headers, so they
work in <img src>, <a href> and from other servers.
- A link is signed and valid for 60 minutes. Ask for a fresh one with Get download links when you need it, rather than storing links.
- In folders with permanent public links,
urlnever expires. It stops working when the folder no longer gives public links or the file is deleted. - Images, audio, video and PDF are shown in the browser; other types (and every
download_url) are downloaded. HTML, SVG and XML files are always downloaded. - Image links accept
?w=and?h=(pixels) for a resized copy, e.g. thumbnails. - Links support
Rangerequests (seeking in audio and video).
Limits
| Limit | Value |
|---|---|
| File size | the folder's limit, at most 100 MB |
| Data per panel | the panel's limit, at most 1 GB by default |
| Metadata | 4 KB of JSON |
Files returned by one list |
100 without paging, up to 1000 with limit_from / limit_to |
ids in url and delete-many |
100 |
| Base64 upload | use upload (multipart) for files larger than a few MB, direct upload for large ones |
Files also count against the storage quota of the panel owner or, for panels of an organization, of the organization.
Common request headers and errors
| Header | Description |
|---|---|
uni-api-key |
Your API key. See Authentication. |
panel-id |
ID of the panel whose files you access. |
Content-Type |
application/json, or multipart/form-data for upload. |
Errors are JSON of the form {"detail": "..."} (see Errors).
These can come from every endpoint:
| Status | detail |
Cause |
|---|---|---|
401 |
API key not provided / API key is invalid |
The key is empty, unknown, disabled or lacks the endpoint's scope. |
403 |
permission denied |
You neither own the panel nor have it shared with you. |
403 |
operation '<operation>' is disabled for folder '<folder>' |
The folder settings set this operation to none. The operation is upload, read, list, update or delete. |
404 |
panel not found |
No panel with this ID, or the panel is disabled. |
404 |
file not found |
No file with this ID or path in the panel. |
422 |
validation error | The panel-id header is missing or not a UUID, or the body does not match the request model. |
429 |
rate limit message | Too many requests; see Rate limits. |
Upload a file
Stores a file. Send it as multipart/form-data.
| Endpoint | POST /api/v3/booster/storage/private/upload |
| Scope | ayeto.booster.storage.write |
| Rate limit | default |
Request
| Form field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | The content. |
path |
string | yes | Target path. A path ending with / is a directory: the uploaded file name is appended. |
metadata |
string | no | JSON object with your data about the file. |
record_collection, record_key |
string, UUID | no | The database record the file belongs to (both or neither). |
overwrite |
boolean | no | Replace a file at the same path (needs the delete right in its folder). Default false. |
rename |
boolean | no | When the path is taken, store as name (1).ext, name (2).ext, … Default false. |
Response
The stored file with links.
Errors
| Status | detail |
Cause |
|---|---|---|
409 |
a file '<path>' already exists |
The path is taken and neither overwrite nor rename was sent. |
422 |
path must start with a folder: '<folder>/<name>' |
The path has only one part. |
422 |
folder name can only contain letters, digits, '_' and '-' |
Invalid folder name. |
422 |
path segments cannot … |
Invalid sub-folder or file name (see Folders and paths). |
422 |
folder '<folder>' does not accept <type> files |
The folder does not accept this file type. |
422 |
the file is larger than <n> MB |
The file exceeds the folder's or the general size limit. |
422 |
the file content is <type>, not <declared> / the file content is not <declared> |
The content does not match the declared image or PDF type. |
422 |
files in this folder must belong to a database record |
The folder requires a record and none was sent. |
422 |
record '<key>' is not in collection '<collection>' |
The record exists in another collection. |
404 |
Database record not found |
No such record in the panel's database. |
403 |
database access is not allowed for this panel |
A record was sent but the panel has no database. |
422 |
metadata must be a JSON object / metadata is larger than 4096 bytes |
Invalid metadata. |
403 |
folder '<folder>' has reached the maximum of <n> files / folder '<folder>' is full |
Folder limit reached. |
403 |
the file storage of this panel is full |
Panel limit reached. |
422 |
storage quota exceeded / organization storage quota exceeded |
The owner's (organization's) storage quota is full. |
Examples
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/upload" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
-F "file=@INV-0042.pdf;type=application/pdf" \
-F "path=invoices/2026/" \
-F 'metadata={"customer": "ACME", "total": 1250}' \
-F "record_collection=orders" \
-F "record_key=8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"
{
"id": "5b0a1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"path": "invoices/2026/INV-0042.pdf",
"folder": "invoices",
"name": "INV-0042.pdf",
"size": 48213,
"content_type": "application/pdf",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"metadata": {"customer": "ACME", "total": 1250},
"record": {"collection": "orders", "key": "8f14e45f-ceea-467a-9b1c-4e1f2a6b3c70"},
"owner": null,
"created": 1791374100000,
"updated": 1791374100000,
"expires_at": null,
"url": "https://ayeto.ai/api/v3/booster/storage/file/3d2c5a8e-…/5b0a1c2d-…/1791377700i.Xk2…/INV-0042.pdf",
"download_url": "https://ayeto.ai/api/v3/booster/storage/file/3d2c5a8e-…/5b0a1c2d-…/1791377700a.p9Q…/INV-0042.pdf"
}
Upload a file as base64
The same as Upload a file with a JSON body, for small files.
| Endpoint | POST /api/v3/booster/storage/private/upload-b64 |
| Scope | ayeto.booster.storage.write |
Request
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Target path; its last part is the file name. |
data |
string | yes | Base64 content or a data URL (data:text/csv;base64,…). |
content_type |
string | no | Declared type (otherwise from the data URL or the file extension). |
metadata, record, overwrite, rename |
no | As in Upload a file; record is {collection, key}. |
Response and errors
As in Upload a file, plus 422 data is not valid base64.
Example
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/upload-b64" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
-H "Content-Type: application/json" \
-d '{"path": "exports/orders-2026-10.csv", "data": "data:text/csv;base64,aWQsdG90YWwKMSwxMjUw"}'
Upload a large file directly
Large files (tens to hundreds of MB) can go straight to the storage service instead of
through the API, in three steps: announce the file, POST it to the returned URL, complete
the upload. Whether direct uploads are available depends on the AYETO installation; when
they are not, upload/init answers 409 direct upload is not available, use the upload route
and you use Upload a file.
| Endpoints | POST /api/v3/booster/storage/private/upload/init, POST /api/v3/booster/storage/private/upload/complete |
| Scope | ayeto.booster.storage.write |
upload/initwithpath,filename(when the path ends with/),content_type,size(exact size in bytes) and optionallymetadata,record,overwrite,rename. The folder rules, type and limits are checked as for an upload. The response is{upload_id, url, fields, expires_at}.POSTtourlasmultipart/form-data: every entry offieldsas a form field, then the file as the fieldfile(it must be the last part). The file must have exactly the announced size and type. This request goes to the storage service, without your API key.upload/completewith{"upload_id": "…"}beforeexpires_at(one hour). The uploaded content is checked again (size, type from the content) and the file is returned. An upload that is never completed is deleted.
| Status | detail |
Cause |
|---|---|---|
409 |
direct upload is not available, use the upload route |
Use Upload a file. |
404 |
upload not found |
Unknown or expired upload_id, or already completed. |
422 |
the file was not uploaded / the uploaded file has another size than announced |
Step 2 did not store the announced file. |
Other errors as in Upload a file.
List files
Returns the files directly in a directory and its sub-folders. Without folder it returns
the panel's top-level folders.
| Endpoint | POST /api/v3/booster/storage/private/list |
| Scope | ayeto.booster.storage |
Request
| Field | Type | Required | Description |
|---|---|---|---|
folder |
string | no | Top-level folder. Without it only folders is returned. |
prefix |
string | no | Sub-folder inside the folder, e.g. 2026 or 2026/q1. |
recursive |
boolean | no | Also files in sub-folders at any depth. Default false. |
params |
object | no | filters, sort_key, sort_order, limit_from, limit_to as in the booster database. Fields: name, path, size, content_type, owner, created, updated, record.collection, record.key, metadata.<path>. Newest first by default. |
links |
boolean | no | Include url and download_url. Default true. |
Response
{"files": [...], "folders": [...]}: files of the page and the names of the
sub-folders directly in the directory.
Example
curl -X POST "https://ayeto.ai/api/v3/booster/storage/private/list" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "panel-id: 3d2c5a8e-7b41-4f0e-9a6d-2c8b1e4f7a90" \
-H "Content-Type: application/json" \
-d '{"folder": "invoices", "prefix": "2026", "params": {"filters": [["metadata.customer", "==", "ACME"]]}}'
Count files
The number of files a list with the same folder, prefix, recursive
(default true here) and params.filters would return across all pages. Without folder,
all files of the panel.
| Endpoint | POST /api/v3/booster/storage/private/count |
| Scope | ayeto.booster.storage |
Get a file
One file with fresh links, by id or path.
| Endpoint | POST /api/v3/booster/storage/private/get |
| Scope | ayeto.booster.storage |
{"path": "invoices/2026/INV-0042.pdf"}
Get download links
Fresh links of up to 100 files.
| Endpoint | POST /api/v3/booster/storage/private/url |
| Scope | ayeto.booster.storage |
Request
| Field | Type | Required | Description |
|---|---|---|---|
ids |
array of UUID | yes | Files (1–100). |
ttl_minutes |
integer | no | Shorter lifetime than the default 60 minutes. |
download |
boolean | no | Links that always download. Default false. |
Response
An array of {id, url, expires_at}; expires_at (milliseconds) is null for permanent
public links.
Update a file
Renames or moves a file, replaces its metadata or its record. A move to another folder
needs the upload right there and the delete right in the current folder, and the file must
fit the new folder's type and size rules. The content never changes; upload with
overwrite to replace it.
| Endpoint | POST /api/v3/booster/storage/private/update |
| Scope | ayeto.booster.storage.write |
Request
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | yes | The file. |
path |
string | no | New path. |
metadata |
object | no | New metadata (replaces the old). |
record |
object | no | New record {collection, key}. |
unlink_record |
boolean | no | Remove the record link. |
Response and errors
The updated file. Errors as in Upload a file; 409 when the new
path is taken.
Delete files
| Endpoints | POST /api/v3/booster/storage/private/delete ({"id": "…"}), POST /api/v3/booster/storage/private/delete-many ({"ids": [...]}, up to 100) |
| Scope | ayeto.booster.storage.write |
Returns {"accepted": true}. Links of a deleted file stop working at once. delete-many
deletes the files in order and stops at the first one that fails.
Storage usage
| Endpoint | POST /api/v3/booster/storage/private/usage (empty body) |
| Scope | ayeto.booster.storage |
{"files": 214, "size": 73400320, "limit": 1073741824, "folders": {"invoices": 52428800, "products": 20971520}}
limit is the panel's limit in bytes (0 = only the owner's storage quota applies).
Events
Files stored, changed (moved, new metadata or record) and deleted through this API are events of the panel like any other: they start workflows with a booster file trigger on the panel, and the panel's webhooks that subscribe to file events receive them. Deletions by retention or together with a record are events too.