AppEngine ships a generic file-storage layer used by every domain — site assets, product images, attachments, customer documents, generated favicons. Files are stored in S3 (or a configured equivalent) under per-org prefixes; the API hides the bucket details.
Endpoints
/repository/file/uploadJWT/repository/file/upload-urlJWT/repository/fileJWT/repository/file/bufferJWT/repository/file/streamJWT/repository/file/urlJWT/repository/file/signurlJWT/repository/file/existsJWT/repository/file/statJWT/repository/file/deleteJWT/repository/file/moveJWT/repository/file/copyJWT/repository/file/createfolderJWT/repository/file/thumbnailsJWT/repository/file/make-privateJWT/repository/file/make-publicJWT/repository/file/flatlist/{prefix}/{pageNumber}JWT/repository/file/flatlistJWTCustomer-scoped variants (write under a per-customer prefix, callable by signed-in customers):
/repository/customer/file/uploadJWT/repository/customer/file/flatlistJWT/repository/customer/file/deleteJWTUpload
The upload endpoint is multipart. The form fields are:
| Field | Type | Description |
|---|---|---|
| file* | binary | The file part. Must be named |
| location | string | Folder prefix the file is stored under. The final key is |
| metadata | string | Optional JSON-stringified metadata stored alongside the object. |
| isPrivate | string |
|
// from base-app/src/lib/appmint-client.ts (excerpt, simplified)
const formData = new FormData();
formData.append('location', 'products/images');
formData.append('files', file);
const res = await fetch('/api/repository/file/upload', {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, orgid: orgId },
body: formData,
});
const saved = await res.json();
// saved.url, saved.location, saved.metadata
Upload from URL
Avoid downloading-then-uploading by letting the server fetch on your behalf:
curl -X POST https://appengine.appmint.io/repository/file/upload-url \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"url":"https://supplier.example/image-1.jpg","location":"products/images"}'
Useful for CSV imports, vendor catalog sync, and AI-generated assets.
Read
POST /repository/file returns the file content. Pass encoding: 'utf8' for text files, omit for base64. For large files use /file/buffer (raw bytes) or /file/stream (returns a presigned stream URL).
curl -X POST https://appengine.appmint.io/repository/file \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"location":"products/images/mug.jpg"}'
Public URLs
Use /repository/file/url to fetch a stable public URL for a file. The shape is:
https://{cdn-host}/{orgId}/{location}
For private files, use /repository/file/signurl to get a time-limited presigned URL:
curl -X POST https://appengine.appmint.io/repository/file/signurl \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"location":"orders/2024/invoice-123.pdf","options":{"expiresIn":3600}}'
Privacy toggles
Files default to public read. Flip a single file or a batch with:
# make private
curl -X POST https://appengine.appmint.io/repository/file/make-private \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"locations":["customer-data/contracts/contract-1.pdf"]}'
# make public
curl -X POST https://appengine.appmint.io/repository/file/make-public \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"locations":["products/hero.jpg"]}'
Listing
flatlist returns a flat (non-recursive) listing for a prefix:
curl 'https://appengine.appmint.io/repository/file/flatlist/products%2Fimages/1' \
-H "orgid: my-org" -H "Authorization: Bearer $JWT"
Or POST for richer filtering (date range, mime type, size):
curl -X POST https://appengine.appmint.io/repository/file/flatlist \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"prefix":"products/images","page":1,"pageSize":50}'
Thumbnails
POST /repository/file/thumbnails triggers thumbnail generation for an image. Sizes are configurable per org; the response contains URLs for each generated variant. Generation is synchronous for small images, queued otherwise.
For ad-hoc transforms (resize, crop, format), pass query params on the file URL — the CDN reads them and serves a transformed copy. Supported params are platform-specific; see the asset CDN docs.
Customer-scoped uploads
Files uploaded through /repository/customer/file/upload live under a per-customer prefix and are listable only by that customer (or a staff user). Use these for KYC documents, support attachments, profile avatars — anywhere the buyer "owns" the file.
curl -X POST https://appengine.appmint.io/repository/customer/file/upload \
-H "orgid: my-org" -H "Authorization: Bearer $CUSTOMER_JWT" \
-H "Content-Type: application/json" \
-d '{"fileName":"receipt-2024.pdf","base64Data":"JVBERi0xLj..."}'
The customer endpoint takes base64 in JSON rather than multipart — handy when you can't post binary form-data (some mobile WebViews, some serverless platforms).
Quotas and limits
- Per-file size cap defaults to 50 MB; configurable per org plan.
- Filenames are sanitized — slashes, control characters, and non-ASCII whitespace are stripped.
- Duplicate names overwrite by default. Pass a unique
location(e.g. prepend a UUID) to keep both. - Deletes are immediate and not soft-deleted. There is no trash for files.
Error envelope
Same as the rest of AppEngine: { statusCode, message, error }. Common cases:
400— missingfilepart, orlocationcontains invalid characters.413— file exceeds the size cap.403— JWT lacksupdatepermission for the storage scope.404—readagainst a missing key (also returned for read-on-private without a signed URL).