Skip to content

Server API ​

Nitro server routes live in server/. They exist for one core reason: secrets — AWS credentials, the Typesense admin key, backend API tokens — must never reach the client bundle. All external service calls that require those credentials go through these routes. The routes also provide a consistent place to enforce authentication, rate limiting, and CSRF protection before touching any sensitive operation.

Route inventory ​

server/
├── api/
│   ├── assets.get.ts
│   ├── browser-os.get.ts
│   ├── csrf-token.get.ts
│   ├── image-proxy.get.ts
│   ├── resolution.post.ts
│   ├── typesense-collection.post.ts   # legacy path
│   ├── typesense-ids.post.ts          # legacy path
│   ├── upload-asset.post.ts
│   ├── s3/
│   │   ├── bucket-config.get.ts
│   │   ├── bucket-config.post.ts
│   │   ├── complete-upload.post.ts
│   │   ├── delete-asset.delete.ts
│   │   ├── download-file.get.ts
│   │   ├── get-display-file.get.ts
│   │   ├── get-signed-url.get.ts
│   │   ├── get-upload-url.get.ts
│   │   ├── resource.get.ts
│   │   ├── start-upload.get.ts
│   │   └── upload.post.ts
│   ├── typesense/
│   │   ├── collections-count.post.ts
│   │   ├── search-collection.post.ts
│   │   ├── search-ids.post.ts
│   │   └── search.post.ts
│   └── zip-viewer/
│       └── extract.post.ts
└── routes/
    ├── apple-app-site-association.get.ts
    ├── health.get.ts
    ├── manifest.json.get.ts
    └── preview_assets.get.ts

CSRF ​

MethodRouteDescription
GET/api/csrf-tokenGenerates a cryptographically random CSRF token, writes it to the XSRF-TOKEN cookie (JS-accessible, 24h), and returns it in the response body. Called once on app load. Implements the Double-Submit Cookie pattern.

The client reads the cookie and sends it as X-XSRF-TOKEN on mutating requests. requireCsrfToken() in server/utils/csrf.ts validates the match.

Typesense routes (server/api/typesense/) ​

The Typesense API key is server-only. These routes proxy search requests, enforce workspace-scoped filtering, and presign any raw S3 keys in results before responding.

MethodRouteDescription
POST/api/typesense/searchMain search proxy. Handles three body shapes: a single-collection search, a multi-search (searches array), and a legacy compat format (request + commonSearchParams). Validates input with Zod, verifies workspace access, composes workspace_id filter, and presigns S3 paths in all hits.
POST/api/typesense/search-collectionRetrieves a single document by collection + id. Presigns S3 paths in the returned doc. Enforces a workspace tenant guard (403 if the doc's workspace_id doesn't match).
POST/api/typesense/search-idsFetches every matching document ID across one or more collections. Paginates at 10,000 results per page. Used by the select-all-across-pages flow for bulk downloads, moves, and shares. Returns Record<collection, id[]>.
POST/api/typesense/collections-countReturns the document count for each of the four DAM collections (digital_assets, digital_assets_categories, dam_collections, digital_assets_tags) plus a total.

Legacy Typesense routes — the two files at server/api/typesense-collection.post.ts and server/api/typesense-ids.post.ts are older implementations of /api/typesense/search-collection and /api/typesense/search-ids respectively. They use a different internal path but serve the same purpose. Prefer the versioned paths under server/api/typesense/.

S3 routes (server/api/s3/) ​

AWS credentials never leave the server. These routes handle the full multipart upload lifecycle, presigned URL generation, object deletion, and resource fetching.

MethodRouteDescription
GET/api/s3/start-uploadInitiates a multipart S3 upload via CreateMultipartUploadCommand. Returns { uploadId, assetId } where assetId is a server-generated UUID used as the S3 object key.
GET/api/s3/get-upload-urlReturns a presigned UploadPart URL for one chunk of a multipart upload. Query params: assetId, PartNumber, UploadId, workspaceId.
POST/api/s3/complete-uploadFinalises a multipart upload via CompleteMultipartUploadCommand. Body: { assetId, UploadId, Parts, workspaceId }.
DELETE/api/s3/delete-assetDeletes an object at {workspaceId}/digital_assets/{assetId}. Body: { assetId, workspaceId }.
GET/api/s3/get-signed-urlReturns a 24h presigned GET URL for {workspaceId}/digital_assets/{assetId}. Enforces alphanumeric-only assetId to block path traversal.
GET/api/s3/get-display-fileSame as get-signed-url (24h presigned GET URL) but without the strict assetId format check. Used for display/preview contexts.
POST/api/s3/uploadSingle-part upload via PutObjectCommand. Accepts multipart form data. Query params: workspaceId, optional compress, key (override the generated UUID), location (override the key prefix), isPublic (set public-read ACL). Returns { assetId, signedUrl }.
GET/api/s3/download-fileProxies a file from a given url query param and streams it back as an attachment (Content-Disposition: attachment). No authentication required.
GET/api/s3/resourceFetches any external URL, encodes the response as a base64 data URI (data:<contentType>;base64,<data>), and returns { data }. Validates the URL against SSRF (blocks localhost, private IPs, cloud metadata endpoints). Auth required.
GET/api/s3/bucket-configReturns the current CORS rules on the configured S3 bucket. Auth required.
POST/api/s3/bucket-configUpdates the S3 bucket CORS rules using the CORS_ALLOWED_ORIGINS environment variable. Auth required.

Asset and upload routes ​

MethodRouteDescription
GET/api/assetsProxies asset download metadata from the backend (/digital-assets/object-download). Query params: assets_id, workspace_id, optional attachment_type. Auth required.
POST/api/upload-assetOrchestrates a full multipart upload end-to-end: validates the file, calls s3/start-upload, s3/get-upload-url for each 10MB chunk, uploads each part directly to S3, calls s3/complete-upload, fetches a signed URL, and registers the asset with the backend API (/digital-assets/upload). If asset_id is provided, registers a new version instead (/digital-assets/version/upload). Rate limited.

Image and media routes ​

MethodRouteDescription
GET/api/image-proxyProxies external images. Validates the url query param against SSRF (blocks private IPs, localhost, cloud metadata). Validates the upstream Content-Type is an image. Rate limited (10 req/min). Auth required.
POST/api/resolutionResizes an image using Jimp. Body: { w, h, s, q, m } where s is a URL or base64 string, q is JPEG quality, and m is the output MIME type. Caps at 600MP and 200MB source. Returns the resized image buffer.

Zip viewer ​

MethodRouteDescription
POST/api/zip-viewer/extractAccepts a zip file upload (multipart, max 100MB). Parses it with adm-zip and returns the first-level hierarchy as Array<{ name, directory? }>. CSRF token required, rate limited.

Utility routes ​

MethodRouteDescription
GET/api/browser-osReturns geo-location data (via the geolocation-db.com third-party API, degrades to null on failure) and parsed user-agent info ({ isMobile, isTablet, isDesktop, browser, version, os, platform }). Used for analytics and download tracking.

Server routes (server/routes/) ​

These are served at the root path (not under /api).

MethodRouteDescription
GET/healthDependency health check. Probes backend API reachability (3s timeout). Returns `{ status: 'ok'
GET/manifest.jsonDynamic PWA manifest. White-label hosts get their brand name and favicon from the backend check-branding endpoint; all others get the default Collage manifest.
GET/apple-app-site-associationiOS Universal Links JSON. Served with Content-Type: application/json at the exact extensionless path Apple requires.
GET/preview_assetsAuthenticated redirect proxy. Forwards query params to the backend preview-image endpoint, validates the returned URL is HTTPS, and issues a 303 redirect.

Server utils (server/utils/) ​

FileKey exportsPurpose
csrf.tsgenerateCsrfToken, setCsrfTokenCookie, requireCsrfToken, validateCsrfTokenDouble-Submit Cookie CSRF implementation. requireCsrfToken throws 403 on mutating requests when the XSRF-TOKEN cookie and X-XSRF-TOKEN header don't match.
rateLimit.tsrequireRateLimit, checkRateLimitIn-memory rate limiter keyed by auth token or IP. Per-route limits: s3/upload and image-proxy at 10/min, everything else at 100/min. Sets standard X-RateLimit-* response headers.
signSearchUrls.tssignSearchResponse, signSingleDoc, readSignerConfigPresigns raw S3 keys ({workspaceId}/digital_assets/...) found in Typesense hits. Maintains a 23h in-memory LRU cache (max 10,000 entries) to avoid re-signing the same key on every search. Used by the typesense/search and typesense-collection routes.
typesense.tsuseTypesense, useS3, getCachedSignedUrl, verifyAndResolveWorkspace, verifyWorkspaceAccess, buildFilter, mapSortBy, signS3PathsInResultsTypesense and S3 client factories, workspace authentication (validates the caller's JWT against the backend /user endpoint and checks workspace membership), filter composition utilities, and S3 URL signing. verifyWorkspaceAccess caches successful auth results for 10s (keyed by SHA-256 of token + workspace ID) to reduce /user calls on high-frequency search paths.
s3/authHelper.tsgetApiBaseUrl, verifyAuthentication, validateWorkspaceIdResolves the backend API base URL from runtime config, verifies a bearer token against the backend /user endpoint (5s timeout), and validates workspace ID format.
s3/fileValidation.tsvalidateFile, validateFileSize, validateMimeType, validateFileExtension, validateFileContent, sanitizeFilename, validateLocationUpload security: enforces 100MB max file size, MIME type and extension allowlisting, magic-number content validation for known types, filename sanitisation (path traversal prevention, 255 char limit), and location parameter sanitisation.
s3/utils.tss3, getS3Client, getS3Bucket, toStream, uuidv4Lazy-initialised AWS S3Client factory. s3 is a Proxy for backward-compat with code that references the client directly. toStream converts a Buffer to a Readable stream for SDK uploads.

Security notes ​

  • All S3 and Typesense routes validate the caller's bearer token against the backend before acting.
  • Workspace-scoped operations (s3/*, typesense/*) additionally verify the caller is a member of the requested workspace.
  • Mutating operations that use CSRF validation are those that go through requireCsrfToken — currently zip-viewer/extract.
  • The image-proxy and s3/resource routes both block SSRF: private IP ranges, loopback, and cloud metadata endpoints (169.254.169.254, metadata.google.internal, metadata.azure.com) are rejected before any outbound request is made.
  • File uploads validate size, MIME type, extension, and magic-number content signature. Filenames are sanitised before use.
  • S3 presigned URLs expire in 24 hours. The server-side cache for signed Typesense results uses a 23h TTL to stay safely within that window.
  • Architecture — Nitro server layer
  • Configuration — runtimeConfig for server secrets (AWS_*, TYPESENSE_*, API_BASE_URL)