Documentation

File storage

Upload endpoints, storage providers, signed URLs, and how files attach to records.

AppEngine has a unified file pipeline that backs the CMS, the storefront product images, document uploads in CRM, AI document processing, and avatar handling. One set of endpoints, one access-control model, multiple providers behind the scenes.

Storage providers

The repository module ships three file providers:

  • Local filesystemrepository.provider.file.ts. Default in dev; files live under the configured upload path.
  • S3repository.provider.file.s3.ts. The production default; handles signed URLs, multipart uploads, and CDN edge integration.
  • AWS replicarepository.provider.aws.ts. For multi-region replication setups.

The active provider is decided per-org by config — the same code paths upload, fetch, and delete regardless of where bytes actually land. Find out which is in use:

GET/repository/file/driverJWT

Uploading

The primary upload endpoint takes a multipart form with a file field.

POST/repository/file/uploadJWT
curl -X POST https://appengine.appmint.io/repository/file/upload \
  -H "orgid: my-org" \
  -H "Authorization: Bearer $JWT" \
  -F "file=@./photo.jpg" \
  -F "path=products/sku-12345/" \
  -F "isPublic=true"

The response includes the storage path, the public URL (when isPublic: true), the content type, and any image metadata (width, height, generated thumbnails). For S3, AppEngine generates xs/sm/md derivatives automatically for image uploads.

For browser-direct uploads that skip the API entirely, ask for a presigned URL:

POST/repository/file/upload-urlJWT
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 '{ "path": "uploads/large-video.mp4", "contentType": "video/mp4" }'

The browser then PUTs directly to S3. Use this for anything over a few MB — saves your AppEngine instance the bandwidth.

Customer uploads

Customer principals (storefront buyers, support requesters) get their own upload route, scoped to their own folder.

POST/profile/customer/file/uploadJWT

Same multipart shape, but the destination path is forced under the customer's namespace, so a buyer can't overwrite another buyer's files.

File operations

Beyond upload, the repository exposes the operations you'd expect from a remote filesystem:

POST/repository/fileJWT
POST/repository/file/bufferJWT
POST/repository/file/streamJWT
POST/repository/file/urlJWT
POST/repository/file/signurlJWT
POST/repository/file/statJWT
POST/repository/file/existsJWT
POST/repository/file/copyJWT
POST/repository/file/moveJWT
POST/repository/file/deleteJWT
POST/repository/file/appendJWT
POST/repository/file/prependJWT

/repository/file/signurl is the most useful one for private content — it returns a time-limited signed URL the browser can fetch directly. Default expiry is 15 minutes; pass expires (seconds) to override.

How files attach to records

Files don't live in their own first-class collection. They're referenced by path or URL inside whichever record needs them — a product's images, a contact's avatar, a page's coverImage. The shape on the record is consistent:

{
  "data": {
    "title": "Sneakers",
    "images": [
      {
        "path": "products/sku-12345/photo.jpg",
        "url": "https://cdn.appmint.io/.../photo.jpg",
        "contentType": "image/jpeg",
        "isPublic": true,
        "meta": {
          "width": 2048,
          "height": 1536,
          "size": 412345,
          "xs": { "path": "...", "url": "..." },
          "sm": { "path": "...", "url": "..." },
          "md": { "path": "...", "url": "..." }
        }
      }
    ]
  }
}

When the upload endpoint returns, you copy the response straight into the parent record's relevant field and update the record. There's no separate "attach" call.

Asset endpoints

A separate set of endpoints — find-asset, update-asset, delete-asset, search-asset — operate on records whose primary purpose is to hold a file (images, videos, PDFs in a media library). They wrap the same file plumbing with a media-library query layer.

POST/repository/find-asset/{datatype}JWT
POST/repository/update-asset/{datatype}/{id}JWT
POST/repository/delete-asset/{datatype}JWT
POST/repository/search-asset/{datatype}JWT

These are the endpoints the admin media browser calls.

Public vs private files

Files are private by default. Two ways to make one public:

  • Set isPublic: true on upload — the file lands in a publicly-readable bucket and the response carries a stable URL.
  • Use signed URLs (/repository/file/signurl) for time-limited shares without changing visibility.

Once a file is public, it stays public until you delete it or move it. Don't put PII or auth tokens in public buckets — there's no way to "unpublish" the URL after a CDN cache.

Avatars and storefront images

The product/avatar pattern uses isPublic: true so the CDN can serve images at scale. Customer-uploaded documents (KYC, support attachments) stay private and need signed URLs.

Favicons and synthetic assets

A small helper exists for generating site favicons from a source image:

POST/repository/file/create_faviconJWT

It crops/resizes a source image into the standard favicon set and writes the bundle to the site folder. Used by the site provisioning flow.