Files live in S3-compatible object storage. The repository controller exposes about 25 file handlers under /repository/file/*, plus a customer-scoped subset and a Pexels-backed stock media search.
Uploading
/repository/file/uploadJWTmultipart/form-data, field name file. Requires the create content permission.
| Form field | Purpose |
|---|---|
file | The file itself. A missing file is 400 Invalid file, null. |
location | Destination folder. Joined as location + '/' + originalname; without it, the original filename is the whole path. |
metadata | Optional metadata; when absent it is derived from the path. |
isPrivate | The string "true" to keep the object private. Anything else makes it publicly readable. |
curl -X POST https://appengine.appmint.io/repository/file/upload \
-H 'Authorization: Bearer …' -H 'orgid: acme' \
-F '[email protected]' \
-F 'location=products/2026' \
-F 'isPrivate=false'The handler tests body.isPrivate === 'true'. It arrives from a multipart form as text, so a JSON boolean true will not match — the object ends up public. Send the literal string.
/repository/file/upload-urlJWTFetches a remote URL and stores it, with the same side effects as a direct upload.
{ "url": "https://example.com/a.png", "location": "imports", "name": "a", "thumbnails": true, "isPrivate": false }The MIME type and extension are sniffed from the download (.svg is special-cased), and the object is written to <location>/<name>[.<ext>].
What an upload does
1. Scope the path to the org
addOrgIdToPath prefixes the tenant, so every key is namespaced.
2. Clear stale thumbnails Any previous thumbnails for that key are deleted first — re-uploading over a path does not leave the old derivatives behind.
3. Write the object
putObject into S3_BUCKET with the derived ContentType.
4. Apply the ACL
Unless isPrivate, the object is set public-read.
5. Generate thumbnails
Via sharp, unless disabled. Failures are logged and swallowed — the upload still succeeds without thumbnails.
6. Return the merged descriptor The file entry from a listing, merged with the thumbnail descriptors.
Thumbnails
Three sizes by default:
| Variant | Width |
|---|---|
xs | 100 |
sm | 200 |
md | 400 |
Override with the IMAGE_SIZES env var (JSON, e.g. {"xs":100,"sm":200,"md":400,"lg":800}). Only image extensions are processed — png, jpeg, jpg, gif, tiff, bmp, webp by default, configurable via IMAGE_EXTENSIONS.
Derivatives are stored beside the original under a thumbnails segment inserted at the second path position, with the size appended to the filename:
acme/products/2026/widget.jpg
acme/thumbnails/products/2026/widget_sm.jpgRegenerate on demand:
/repository/file/thumbnailsJWTTakes { location } — a single path or an array.
These variants are what BaseModel.post.images.meta.xs|sm|md points at, which is why an image referenced from a record already has responsive sizes available.
Reading files
/repository/fileJWT/repository/file/bufferJWT/repository/file/streamJWT/repository/file/urlJWT/repository/file/signurlJWT/repository/file/statJWT/repository/file/existsJWT/repository/file/get_assetJWTA signed URL grants access to the object to whoever holds it. Do not put one in a page that is cached or indexed, and re-issue rather than storing them long-term.
Managing files
/repository/file/copyJWT/repository/file/moveJWT/repository/file/deleteJWT/repository/file/appendJWT/repository/file/prependJWT/repository/file/createfolderJWT/repository/file/make-privateJWT/repository/file/make-publicJWT/repository/file/driverJWTappend and prepend are fully supported only by the local driver; on S3 they delegate to the underlying disk.
Listing
/repository/file/flatlistJWT/repository/file/flatlist/:prefix/:pageNumberJWTPaged, 100 keys per page by default. ?check-privacy=true resolves each object's ACL, which costs an extra call per key.
Listing options support prefix, maxKeys, pageNumber, startPage, recursive, endsWith, includes, thumbnail, foldersOnly and checkPrivacy.
/repository/file/indexJWTIndexes a file location so it becomes searchable.
Favicons
/repository/file/create_faviconJWTBuilds favicon.ico and favicon.png at the org bucket root from a source image (sharp + to-ico) and returns { ico: { signedUrl, path }, png: { signedUrl, path } }.
Customer-scoped files
Three parallel endpoints let an end user manage their own uploads without reaching the operator surface:
/repository/customer/file/uploadJWT/repository/customer/file/flatlistJWT/repository/customer/file/deleteJWTStock media
Pexels-backed search, so the Studio asset picker can offer stock imagery inline:
/repository/media/photosJWT/repository/media/photos/curatedJWT/repository/media/videosJWT/repository/media/videos/popularJWTAssets
Assets are records that describe files — searchable, taggable, and attached to other records.
/repository/find-asset/:datatypeJWT/repository/search-asset/:datatypeJWT/repository/update-asset/:datatype/:idJWT/repository/delete-asset/:datatypeJWTStorage configuration
| Variable | Purpose |
|---|---|
S3_BUCKET | Bucket name |
S3_KEY, S3_SECRET | Credentials |
S3_ENDPOINT | Endpoint — set for S3-compatible providers |
S3_REGION | Region |
FILE_PATH | Base path within the disk |
IMAGE_SIZES | Thumbnail sizes, JSON |
IMAGE_EXTENSIONS | Extensions eligible for thumbnails, JSON |
The direct S3 client used by put, createThumbnails and createFavicon writes to S3_BUCKET with the org id as a path prefix. The flydrive StorageManager built in getStorage is configured for a per-org bucket — S3_BUCKET + '-' + orgId. If you are provisioning storage or auditing where a file landed, check which of the two the operation went through.