# Files

Unified file storage endpoint for all file types. Use ?type=document (private PDFs), ?type=image (public CDN/R2 images), or ?type=secure (confidential private files). Single-resource routes (/files/:id) auto-detect the type.

**Base URL:** `https://api.trustpager.com/functions/v1/api/v1`

## Endpoints

### GET /files?type=document|image|secure

List files. The "type" query parameter is required. "document" = private PDFs (25MB max), "image" = public CDN media (10MB max), "secure" = confidential private files (50MB max).

**Scopes:** `files:read` — [full detail](./files/get-files-type-document-image-secure.md)

### GET /files/:id

Get file metadata. Auto-detects the file type (document, image, or secure) from the ID. Returns normalized fields: id, type, name, mime_type, file_size, folder, description, uploaded_by, created_at, plus type-specific fields (public_url for document, download_url for image, file_category for secure).

**Scopes:** `files:read` — [full detail](./files/get-files-id.md)

### GET /files/:id/download

Get a temporary signed download URL for any file type (expires in 60 seconds). Auto-detects type. For public R2 images, returns the permanent r2_url instead.

**Scopes:** `files:read` — [full detail](./files/get-files-id-download.md)

### PUT /files/:id

Update file metadata (name, description, folder, and type-specific fields). Auto-detects type. For documents: name, description, folder, document_type. For images: name, folder, description. For secure: name, description, folder, file_category.

**Scopes:** `files:write` — [full detail](./files/put-files-id.md)

### DELETE /files/:id

Delete a file and remove it from storage. Auto-detects type. For images: removes from R2. For documents and secure files: removes from Supabase private storage.

**Scopes:** `files:write` — [full detail](./files/delete-files-id.md)

### POST /files/upload

Upload a file via base64 content. "type" determines storage backend: "image" stores on public R2/CDN (returns r2_url), "document" stores in private Supabase bucket, "secure" stores in private Supabase bucket with confidential access. Size limits: image=10MB, document=25MB, secure=50MB.

**Scopes:** `files:write` — [full detail](./files/post-files-upload.md)

### POST /files/:id/publish

Publish a private document to a public URL via R2. Creates a publicly accessible copy. Returns public_url.

**Scopes:** `files:write` — [full detail](./files/post-files-id-publish.md)

### POST /files/:id/unpublish

Remove the public URL for a previously published document. The file remains in private storage.

**Scopes:** `files:write` — [full detail](./files/post-files-id-unpublish.md)

### POST /files/:id/make-public

Move an image or secure file from private storage to the public CDN. For image files (type=image, is_private=true): the file is moved from private R2 to the public bucket and is_private is set to false -- the original private copy is removed. For secure files (type=secure): the file is copied from Supabase private storage to the public CDN and public_url is set -- the original private copy is retained. Returns the public URL of the published file. Use POST /files/:id/publish for documents (type=document).

**Scopes:** `files:write` — [full detail](./files/post-files-id-make-public.md)

### POST /files/:id/make-private

Move an image or secure file from the public CDN back to private storage. For image files (type=image, is_private=false): the file is moved from the public bucket to private R2 and is_private is set to true -- the public URL is no longer accessible. For secure files (type=secure): the public CDN copy is deleted and public_url is cleared -- the original private copy is retained. Use POST /files/:id/unpublish for documents (type=document).

**Scopes:** `files:write` — [full detail](./files/post-files-id-make-private.md)

### GET /files/:id/signed-url

Generate a short-lived signed URL for a private image file stored in private R2. Only applicable to image files (type=image) with is_private=true. The signed URL expires after 600 seconds (10 minutes) and allows temporary read access without making the file permanently public. Returns the signed URL, expiry timestamp, and MIME type.

**Scopes:** `files:read` — [full detail](./files/get-files-id-signed-url.md)

### GET /files/folders?type=document|image|secure

List distinct folder names for a given file type. Use type="document", "image", or "secure".

**Scopes:** `files:read` — [full detail](./files/get-files-folders-type-document-image-secure.md)

### POST /files/folders?type=document|image|secure

Create a new folder for a given file type. Pass type as a query parameter and name in the body.

**Scopes:** `files:write` — [full detail](./files/post-files-folders-type-document-image-secure.md)

### POST /files/bundle

Bundle up to 50 files (any mix of document / image / secure types -- auto-detected) into a ZIP archive. Default mode ("binary") streams the ZIP back directly as application/zip (suitable for browser save dialogs). Pass ?response=url (or "response": "url" in the body) to stage the ZIP and return a 10-minute signed download URL instead -- use this mode with API agents that cannot consume a large binary response. Cap: 50 files / 200MB total decompressed. Files that cannot be fetched from storage are listed in a _manifest.txt included in the ZIP and in the skipped[] field of the url-mode response. Requires files:read scope (no write scope needed).

**Scopes:** `files:read` — [full detail](./files/post-files-bundle.md)

### POST /images/optimize

Generate web-optimized WebP variants (mobile 640px, tablet 1024px, desktop 1920px) from an existing image file or a public image URL. RECOMMENDED WORKFLOW: generate an image with POST /ai/generate-image (returns lossless PNG) then call this endpoint to produce lean WebP variants ready for web delivery. Pass file_id to optimize an existing company_files entry -- variants are persisted to R2 and website_images rows are created, and the original file's is_optimized_image metadata is updated. Pass image_url for ad-hoc optimization of an external image -- variants are uploaded to R2 but no website_images rows are written. Idempotent: re-optimizing a file_id replaces previous variants. Costs credits when the platform prices the "image_optimize" feature; otherwise free.

**Scopes:** `files:write` — [full detail](./files/post-images-optimize.md)
