Files¶
Endpoints for uploading, registering, attaching, and retrieving files associated with mentorships.
Base path: /api/files
Authentication: All endpoints require a valid session and an organization context.
Endpoints¶
| Method | Path | Min Role | Description |
|---|---|---|---|
POST |
/api/files/presign |
Any | Request a presigned PUT URL for direct S3/R2 upload |
POST |
/api/files/register |
Any | Register a file in the database after a direct upload |
POST |
/api/files/upload |
Any | Upload a file directly via multipart form (legacy) |
POST |
/api/files/:fileId/attach |
Coach |
Attach a registered file to a mentorship object |
DELETE |
/api/files/:fileId/attach/:objectType/:objectId |
Coach |
Detach a file from an object |
GET |
/api/files/:fileId |
Any | Get file metadata and a presigned download URL |
GET |
/api/files/mentorship/:mentorshipId/attachments |
Any | List files attached to a mentorship |
Upload Flow¶
The recommended upload flow avoids proxying binary data through the backend:
1. POST /api/files/presign → { presignedUrl, storageKey }
2. PUT <presignedUrl> → upload directly to S3/R2 (browser XHR, with progress)
3. POST /api/files/register → { fileId }
4. POST /api/files/:id/attach → { attachmentId }
Allowed File Types and Size Limits¶
| MIME type pattern | Max size |
|---|---|
image/* |
100 MB |
video/* |
500 MB |
text/* |
100 MB |
application/pdf |
100 MB |
application/zip, application/x-zip-compressed |
100 MB |
Requests exceeding these limits or using an unsupported MIME type are rejected before any upload occurs.
POST /api/files/presign¶
Request a presigned PUT URL to upload a file directly to S3/R2 storage. After a successful PUT, call POST /api/files/register to create the database record.
Request body:
{
"filename": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 204800,
"prefix": "mentorships"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
filename |
string | Yes | Original filename including extension |
mimeType |
string | Yes | Must be an allowed MIME type (see table above) |
sizeBytes |
integer | Yes | File size in bytes; must not exceed the type limit |
prefix |
string | No | Storage key prefix (e.g. mentorships, avatars); default: uploads |
Response 200:
{
"data": {
"presignedUrl": "https://r2.example.com/bucket/mentorships/uuid.pdf?X-Amz-Signature=...",
"storageKey": "mentorships/uuid.pdf"
}
}
Error responses:
| Code | Condition |
|---|---|
400 |
Unsupported MIME type |
413 |
File size exceeds the limit for this MIME type |
POST /api/files/register¶
Create the database record for a file that was uploaded directly to S3/R2 using a presigned URL.
Request body:
{
"storageKey": "mentorships/uuid.pdf",
"filename": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 204800
}
| Field | Type | Required | Description |
|---|---|---|---|
storageKey |
string | Yes | The key returned by POST /api/files/presign |
filename |
string | Yes | Original filename |
mimeType |
string | Yes | MIME type of the file |
sizeBytes |
integer | Yes | File size in bytes |
Response 201:
POST /api/files/upload¶
Upload a file directly to the backend using multipart form data. Includes optional targets for automatically updating avatar or organization logo.
Prefer presign flow
The presign → direct-upload → register flow is preferred for large files and provides upload progress feedback. Use /api/files/upload only for simple cases.
Request body: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | Yes | The file to upload |
prefix |
string | No | Storage key prefix (default: uploads) |
target |
string | No | avatar to update the current user's avatar; orgLogo to update an org logo |
organizationId |
string | No | Required when target = 'orgLogo' |
Response 201:
{
"data": {
"fileId": "file-uuid",
"sha256Hash": "abc123...",
"storageKey": "uploads/uuid.pdf",
"mimeType": "application/pdf",
"sizeBytes": 204800
}
}
Error responses:
| Code | Condition |
|---|---|
400 |
file field missing or MIME type not allowed |
413 |
File size exceeds the limit |
POST /api/files/:fileId/attach¶
Attach a registered file to a mentorship object.
Min role: Coach
Access rules:
Coachcan only attach files to their own mentorships.Manager,OrganizationAdmin,PlatformAdmincan attach to any mentorship in their org.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
fileId |
string | File UUID |
Request body:
| Field | Type | Required | Constraints |
|---|---|---|---|
objectType |
string | Yes | Object type to attach to. Currently only mentorship is supported |
objectId |
string | Yes | UUID of the target object |
Response 201:
{
"data": {
"attachmentId": "attachment-uuid",
"fileId": "file-uuid",
"objectType": "mentorship",
"objectId": "mentorship-uuid"
}
}
Error responses:
| Code | Condition |
|---|---|
403 |
Coach attaching to another coach's mentorship; org mismatch |
404 |
File not found; or target mentorship not found |
409 |
File is already attached to this object |
DELETE /api/files/:fileId/attach/:objectType/:objectId¶
Detach a file from an object. If this was the last attachment for the file, the file record and the underlying storage object are deleted (orphan cleanup).
Min role: Coach
Path parameters:
| Parameter | Type | Description |
|---|---|---|
fileId |
string | File UUID |
objectType |
string | Object type the file is attached to |
objectId |
string | UUID of the object |
Response: 204 No Content
Error responses:
| Code | Condition |
|---|---|
400 |
Missing path parameters |
403 |
Caller does not have access to this attachment |
404 |
Attachment not found |
GET /api/files/:fileId¶
Get metadata for a file and a short-lived presigned URL to download it.
Access rules:
PlatformAdmin: can access any file.- Direct participants (coach or teacher of the mentorship the file is attached to) can access the file.
Manager/OrganizationAdmincan access files within their org.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
fileId |
string | File UUID |
Response 200:
{
"data": {
"id": "file-uuid",
"filename": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 204800,
"sha256Hash": "abc123...",
"presignedUrl": "https://r2.example.com/bucket/mentorships/uuid.pdf?X-Amz-Signature=...",
"createdAt": "2025-04-01T12:00:00.000Z"
}
}
Error responses:
| Code | Condition |
|---|---|
403 |
Caller does not have access to this file |
404 |
File not found |
GET /api/files/mentorship/:mentorshipId/attachments¶
List all files attached to a specific mentorship. Each file includes a short-lived presigned download URL.
Access rules:
PlatformAdmin: can list attachments for any mentorship.- Coach and teacher participants can access files from their own mentorship.
Manager/OrganizationAdmincan access files within their org.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
mentorshipId |
string | Mentorship UUID |
Response 200:
{
"data": [
{
"attachmentId": "attachment-uuid",
"fileId": "file-uuid",
"filename": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 204800,
"presignedUrl": "https://...",
"createdAt": "2025-04-01T12:00:00.000Z"
}
]
}
Error responses:
| Code | Condition |
|---|---|
403 |
Caller does not have access to this mentorship's files |
404 |
Mentorship not found |
R2 / S3 CORS Configuration¶
When using the presigned upload flow, the storage bucket must allow PUT requests from the frontend origin. Configure this in the Cloudflare R2 dashboard:
Cloudflare R2 → Bucket → Settings → CORS policy: