API Reference
This reference covers the HTTP route handlers currently implemented in secure-vault/src/app/api.
API Conventions
Authentication
- Most authenticated routes rely on the
__Secure-sessioncookie. - Shared-link routes use a token in the URL and may additionally require a share access session created after OTP verification.
- Cron routes require
Authorization: Bearer {CRON_SECRET}.
Response Style
- Successful responses are JSON unless the route streams a file.
- Download and preview endpoints stream decrypted file content.
- Error responses typically return
{ message: string }or{ error: string }. - Some semantic endpoints use structured route error payloads with machine-readable codes.
Product Limits And Supported Types
| Contract | Current value |
|---|---|
| Upload chunk size | 5 MiB |
| Max upload size | 100 MiB |
| Max active uploads per user | 3 |
| Default storage quota | 1 GiB |
| Trash retention | 30 days |
| Uploadable image types | image/jpeg, image/png, image/webp, image/gif, image/avif |
| Uploadable document types | application/pdf |
| Max PDF size for semantic indexing | 10 MiB |
Shared Preview Security
Shared preview routes are access-controlled and deterrent-based.
/s/{token}is server-rendered after token and restricted-session checks.- Shared preview byte routes repeat authorization before returning content.
- Restricted links tie access to allowed email addresses through OTP verification.
- Shared image and PDF page previews are rendered in the browser as CSS backgrounds from local
blob:URLs rather than native image elements. - Context-menu and common inspection shortcuts are blocked on shared pages as a deterrent.
- These controls reduce casual saving and inspection; they do not prevent screenshots or determined inspection by a verified viewer.
See Shared Preview Protection for the full threat model.
Encryption Model
- File encryption is server-managed.
- The application encrypts chunks before storing them in R2 and decrypts them on authorized reads.
- This is not a client-side or end-to-end encryption contract.
Rate Limiting
Implemented with Redis-backed fixed windows, failing open outside production if Redis is unavailable.
| Policy | Limit | Window |
|---|---|---|
| Login | 5 | 15 minutes |
| Signup | 5 | 1 hour |
| Share OTP request | 3 | 15 minutes |
| Share OTP verify | 3 | 5 minutes |
| Password reset request | 3 | 15 minutes |
| Password reset verify | 5 | 5 minutes |
| Upload | 100 | 1 minute |
| Download | 30 | 1 minute |
Auth Routes
GET /api/auth/current-user
- Auth required: yes
- Purpose: return the current authenticated user in client-safe form
- Success response:
200with{ user }
- Common failures:
401unauthorized
POST /api/auth/password-reset/request-otp
- Auth required: no
- Purpose: request a password reset OTP for an email address
- Request body:
email
- Behavior:
- validates email presence
- rate-limits by IP and IP+email
- returns a generic success response even when the email does not exist
- Success response:
200with a generic success message
- Common failures:
400validation error429rate-limited
POST /api/auth/password-reset/reset
- Auth required: no
- Purpose: reset a password with email, OTP code, and new password
- Request body:
emailcodenewPassword
- Behavior:
- validates password strength
- verifies OTP
- updates password
- deletes all active sessions for the user
- Success response:
200with success message
- Common failures:
400validation error403invalid, expired, used, or locked OTP429rate-limited500reset failure
File Metadata Routes
GET /api/files
- Auth required: yes
- Purpose: list ready, non-deleted files for the current user
- Success response:
200with{ files }
- Common failures:
401invalid credentials500load failure
GET /api/files/explorer
- Auth required: yes
- Purpose: load the explorer dataset used by the Files page
- Success response:
200with{ files, folders }
- Common failures:
401invalid credentials500load failure
GET /api/files/storage-dashboard
- Auth required: yes
- Purpose: load storage analytics and quota information
- Success response:
200with dashboard data including quota, active bytes, trash bytes, category breakdown, and largest files
- Common failures:
401invalid credentials500load failure
GET /api/files/trash
- Auth required: yes
- Purpose: load root trash items plus summary information
- Success response:
200with trash page data
- Common failures:
401invalid credentials500load failure
GET /api/files/trash/summary
- Auth required: yes
- Purpose: load lightweight trash counters
- Success response:
200with trash summary
- Common failures:
401invalid credentials500load failure
File Streaming Routes
GET /api/files/:id/download
- Auth required: yes
- Purpose: stream an owned file as an attachment
- Behavior:
- verifies ownership
- rate-limits download traffic
- decrypts and streams the file
- Success response:
200streamed file response
- Common failures:
401invalid credentials404file not found429rate-limited500stream failure
GET /api/files/:id/preview
- Auth required: yes
- Purpose: stream an owned file inline for preview
- Behavior:
- same security path as download
- uses inline disposition
- Success response:
200streamed file response
- Common failures:
- same as download
Upload Routes
POST /api/upload/init
- Auth required: yes
- Purpose: initialize a resumable upload session
- Request body:
fileNamefileSizefileType
- Behavior:
- validates body
- checks quota and file size
- creates file metadata and upload session records
- Success response:
200with{ fileId, uploadId, totalChunks }
- Common failures:
401invalid credentials400invalid request413or4xxupload initialization constraint failures429rate-limited500initialization failure
Example request:
json
{
"fileName": "quarterly-report.pdf",
"fileSize": 7340032,
"fileType": "application/pdf"
}Example success response:
json
{
"fileId": "file_123",
"uploadId": "upload_123",
"totalChunks": 2
}GET /api/upload/status?uploadId=...
- Auth required: yes
- Purpose: fetch resumable upload state
- Query parameters:
uploadId
- Success response:
200with upload status, completed chunk indexes,fileId,uploadId, andtotalChunks
- Common failures:
401invalid credentials400invalid query404upload not found429rate-limited500lookup failure
POST /api/upload/start
- Auth required: yes
- Purpose: claim an upload concurrency slot before uploading chunks
- Request body:
uploadId
- Success response:
200with{ uploadId, activeCount, maxActiveUploads }
- Common failures:
401invalid credentials404unknown or unauthorized upload session429no slot available, includesRetry-After500claim failure
POST /api/upload/chunk
- Auth required: yes
- Purpose: upload one chunk body for an active upload session
- Required headers:
x-upload-idx-chunk-index
- Request body:
- raw chunk bytes
- Behavior:
- encrypts the chunk
- writes encrypted data to R2
- writes IV, auth tag, and R2 key metadata to
file_chunks
- Success response:
200with chunk result payload409may indicate the chunk already exists and can be treated as completed by the client
- Common failures:
401invalid credentials400invalid headers or body429rate-limited or lease conflict500upload failure
Request contract summary:
- send raw bytes as the body
- set
x-upload-idto the upload session ID - set
x-chunk-indexto the zero-based chunk index
POST /api/upload/complete
- Auth required: yes
- Purpose: finalize a resumable upload after all chunks are present
- Request body:
uploadId
- Behavior:
- validates upload state
- marks the file ready
- releases the upload slot in a
finallyblock
- Success response:
200with completion payload
- Common failures:
400invalid request403invalid credentials4xxtransaction validation failures500internal server error
Example request:
json
{
"uploadId": "upload_123"
}POST /api/upload/release
- Auth required: yes
- Purpose: manually release a claimed upload slot
- Request body:
uploadId
- Success response:
200with{ released: true, uploadId }
- Common failures:
401invalid credentials400invalid request404unknown upload session500release failure
Search Routes
GET /api/search/files?q=...&limit=...
- Auth required: yes
- Purpose: exact or prefix-style filename search scoped to the current user
- Query parameters:
qlimitoptional, default20
- Success response:
200with{ query, results }
- Common failures:
400missing or too-short query401invalid credentials500search failure
POST /api/search/semantic
- Auth required: yes
- Purpose: run semantic or hybrid search over indexed content
- Request body:
querylimitoptional, max25
- Behavior:
- requires semantic indexing to be enabled
- embeds the query
- combines semantic ranking with scoped metadata retrieval
- Success response:
200with{ query, limit, results }
- Common failures:
400invalid request401unauthenticated503semantic indexing disabled or unavailable
Example request:
json
{
"query": "invoice signed in March",
"limit": 10
}Embeddings Routes
POST /api/embeddings
- Auth required: yes
- Purpose: create, retry, or reindex a semantic indexing job for a file
- Request body:
fileIdmodalityaspdforimageactionoptional:enqueue,retry, orreindex
- Success response:
202with job state payload
- Common failures:
400invalid request401unauthenticated404file or job target not found409job conflict503semantic indexing unavailable
Example request:
json
{
"fileId": "file_123",
"modality": "pdf",
"action": "enqueue"
}GET /api/embeddings/:fileId
- Auth required: yes
- Purpose: fetch semantic indexing status for the current user’s file
- Success response:
200with a list of jobs and their statuses
- Common failures:
401unauthenticated404not found503status unavailable
Share Routes
GET /api/share/links?fileId=... or GET /api/share/links?folderId=...
- Auth required: yes and email-verified
- Purpose: list active share links owned by the current user for a single file or folder
- Query parameters:
- either
fileIdorfolderId
- either
- Success response:
200with share link list
- Common failures:
400invalid target query401unauthorized500load failure
GET /api/share/:token/folder?folderId=...
- Auth required: token-based, plus share access session for restricted links
- Purpose: browse a shared folder and optionally descend into a child folder
- Query parameters:
folderIdoptional, defaults to the share root folder
- Success response:
200with breadcrumb, current folder, child files, and child folders
- Common failures:
403access denied404link not found410expired link500load failure
POST /api/share/:token/request-otp
- Auth required: no normal session required
- Purpose: request a share-access OTP for a restricted share link
- Request body:
email
- Behavior:
- returns generic success when an email is not allowed to avoid disclosing allowlist membership
- Success response:
200with generic success
- Common failures:
400email required429rate-limited500request failure
Example request:
json
{
"email": "reviewer@example.com"
}POST /api/share/:token/verify-otp
- Auth required: no normal session required
- Purpose: verify share-access OTP and create a share access session
- Request body:
emailcode
- Success response:
200with{ success: true }
- Common failures:
400missing email or code403invalid OTP or access denied429rate-limited500verification failure
Example request:
json
{
"email": "reviewer@example.com",
"code": "123456"
}POST /api/share/:token/logout
- Auth required: token must reference a valid share link
- Purpose: clear the share access session associated with the link
- Success response:
200with{ success: true }
- Common failures:
404share link not found500logout failure
GET /api/share/:token/download
- Auth required: token-based, plus share access session for restricted links
- Purpose: download a shared file or a file inside a shared folder
- Query parameters:
fileIdrequired when the share target is a folder
- Behavior:
- validates link accessibility
- validates OTP access if needed
- enforces max download count
- records access activity
- streams decrypted content
- Success response:
200streamed file response
- Common failures:
403access denied or download limit reached404link or file not found410expired link429rate-limited500stream failure
GET /api/share/:token/preview
- Auth required: token-based, plus share access session for restricted links
- Purpose: preview a shared file inline
- Query parameters:
fileIdrequired when previewing a file inside a shared folder
- Behavior:
- similar to shared download, but does not increment max-download enforcement logic
- applies protected shared-preview headers
- Success response:
200streamed inline response
- Response headers:
Cache-Control: private, no-storeContent-Disposition: inlineCross-Origin-Resource-Policy: same-originReferrer-Policy: no-referrerX-Content-Type-Options: nosniffX-Robots-Tag: noindex, noarchive
- Common failures:
- similar to shared download except download-limit enforcement is not the main path
GET /api/share/:token/pdf-preview
- Auth required: token-based, plus share access session for restricted links
- Purpose: return the shared PDF preview manifest for a file or a file inside a shared folder
- Query parameters:
fileIdrequired when the share target is a folder
- Behavior:
- validates link accessibility
- validates OTP access if needed
- validates folder-subtree access when
fileIdis supplied for a folder share - reads PDF metadata and existing preview-page state
- records one share access event on manifest success
- does not increment download count
- Success response:
200with{ fileId, fileName, mimeType, pageCount, renderVersion, pages }
- Common failures:
403access denied404share link not found or invalid folder target410expired share link413PDF too large or too many pages for secure preview415file is not a PDF422PDF cannot be parsed or rendered for secure preview503preview feature disabled or renderer unavailable
Manifest shape summary:
json
{
"fileId": "file_123",
"fileName": "report.pdf",
"mimeType": "application/pdf",
"pageCount": 3,
"renderVersion": 1,
"pages": [
{
"page": 1,
"status": "ready",
"width": 1240,
"height": 1754,
"src": "/api/share/share-token/pdf-preview/pages/1"
}
]
}GET /api/share/:token/pdf-preview/pages/:page
- Auth required: token-based, plus share access session for restricted links
- Purpose: return one rendered shared PDF page as
image/webp - Query parameters:
fileIdrequired when the share target is a folder
- Behavior:
- validates link accessibility before any cache lookup
- validates OTP access if needed
- validates folder-subtree access when
fileIdis supplied for a folder share - checks Redis for a short-lived page-response cache
- falls back to the durable encrypted derivative in R2 or on-demand rendering when Redis misses
- does not record per-page share access
- does not increment download count
- Success response:
200streamed image response withContent-Type: image/webp
- Response headers:
Cache-Control: private, no-storeContent-Disposition: inlineCross-Origin-Resource-Policy: same-originReferrer-Policy: no-referrerX-Content-Type-Options: nosniffX-Robots-Tag: noindex, noarchiveX-Preview-Cache: hitwhen served from RedisX-Preview-Cache: misswhen Redis was bypassed and the route used the preview service
- Common failures:
400invalid page number403access denied404share link not found, invalid folder target, or page out of range410expired share link413PDF too large or too many pages for secure preview415file is not a PDF422PDF page cannot be rendered503preview feature disabled or renderer unavailable
Server-side cache notes:
- Redis key shape:
share:pdf-preview:page:{token}:{fileId}:{pageNumber}:v{renderVersion} - Redis TTL:
min(remaining share-link lifetime, 24 hours) - browser responses are intentionally
no-store, so the hot cache remains server-side only
Cron Routes
GET /api/cron/cleanup
- Auth required: bearer cron secret
- Purpose: purge expired trash and clean stale uploads
- Success response:
200with{ trash, uploads }
- Common failures:
401unauthorized500cleanup failure
POST /api/cron/embeddings?limit=...
- Auth required: bearer cron secret
- Purpose: requeue semantic indexing retry candidates
- Query parameters:
limitoptional, max100
- Success response:
200with retry sweep result
- Common failures:
400invalid request403invalid cron credentials503semantic indexing disabled or unavailable
Related Server Actions
Not all mutations use route handlers. Important internal mutation surfaces are implemented as Next.js server actions:
- auth: login, signup
- dashboard: logout, profile update, password change, revoke session, revoke other sessions
- files: rename, move, bulk move, bulk delete, create folder, soft delete
- share: create link, revoke link, update share settings
- trash: restore, permanently delete, empty trash
These actions are part of the application contract even though they are not public JSON APIs.
Important distinction:
- if you are integrating from another service, use the documented HTTP routes in this file
- if you are extending the web application itself, many write operations are implemented as server actions rather than external APIs