Skip to main content

Authentication

All upload endpoints require authentication via tRPC context. The user must be logged in.

Simple Upload

requestUploadUrl

Request a presigned URL for direct file upload.
filename
string
required
Name of the file being uploaded
mimeType
string
required
MIME type of the file (e.g., image/jpeg, video/mp4)
size
number
required
File size in bytes
Response:
{
  uploadUrl: string; // Presigned S3 URL for PUT request
  fileId: string; // Database file record ID
  key: string; // S3 object key
}
Example:
const { uploadUrl, fileId } = await trpc.upload.requestUploadUrl.mutate({
  filename: "photo.jpg",
  mimeType: "image/jpeg",
  size: 1024000,
});

// Upload directly to S3
await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": "image/jpeg" },
  body: fileBlob,
});

confirmUpload

Mark a file as successfully uploaded.
fileId
string
required
File record ID from requestUploadUrl
Response:
{
  success: boolean;
  file: {
    id: string;
    filename: string;
    url: string;
  }
}

Multipart Upload

For files larger than 10MB, use multipart upload for resumability.

initiateMultipart

Start a new multipart upload.
filename
string
required
Name of the file
mimeType
string
required
MIME type of the file
size
number
required
Total file size in bytes
totalParts
number
required
Number of parts the file will be split into
Response:
{
  fileId: string; // Database file record ID
  uploadId: string; // S3 multipart upload ID
  key: string; // S3 object key
}

getPartUrl

Get presigned URL for uploading a specific part.
fileId
string
required
File record ID
partNumber
number
required
Part number (1-indexed)
Response:
{
  uploadUrl: string; // Presigned URL for this part
}

completePart

Record that a part has been uploaded successfully.
fileId
string
required
File record ID
partNumber
number
required
Part number that was uploaded
etag
string
required
ETag returned from S3 after uploading the part
Response:
{
  success: boolean;
  uploadedParts: number[];  // Array of completed part numbers
}

completeMultipart

Finalize the multipart upload after all parts are uploaded.
fileId
string
required
File record ID
Response:
{
  success: boolean;
  file: {
    id: string;
    filename: string;
    url: string;
  }
}

abortMultipart

Cancel a multipart upload and clean up parts.
fileId
string
required
File record ID
Response:
{
  success: boolean;
}

Error Codes

CodeDescription
UNAUTHORIZEDUser not authenticated
BAD_REQUESTInvalid file type or size
QUOTA_EXCEEDEDUser storage quota exceeded
NOT_FOUNDFile record not found
INTERNAL_SERVER_ERRORS3 or database error

Allowed MIME Types

const ALLOWED_MIME_TYPES = [
  "image/jpeg",
  "image/png",
  "image/gif",
  "image/webp",
  "application/pdf",
  "video/mp4",
  "video/quicktime",
];
To add more types, modify ALLOWED_MIME_TYPES in apps/api/src/routers/upload.ts.

Size Limits

LimitValue
Simple upload max10 MB
Multipart part size5 MB
Maximum file size5 GB
Default user quota500 MB

Test Checklist

  • requestUploadUrl returns a presigned URL
  • Upload succeeds and confirmUpload marks the file as uploaded
  • Multipart uploads complete successfully

Troubleshooting

If you see BAD_REQUEST or QUOTA_EXCEEDED, confirm allowed MIME types and storage quota configuration.

Remove / Disable

To disable uploads while you configure S3, set: apps/mobile/features/feature-registry.tsxfeatureFlags.fileUploads = false For production removal guidance, see Removing Features.