Skip to content

Uploads API

All endpoints require authentication. Files are stored in Cloudflare R2.

POST /api/uploads

Body: multipart/form-data with a file field.

Accepted types: JPEG, PNG, GIF, WebP, SVG. Max size: 5 MB. File content is validated against magic number bytes to prevent type spoofing.

Response: { url } — relative URL (e.g. /api/uploads/images/<id>.png).

GET /api/uploads/images/:id

Returns the image with appropriate Content-Type and immutable caching (Cache-Control: public, max-age=31536000, immutable).


POST /api/uploads/recruitment/attachments

Body: multipart/form-data with a file field.

Accepted types: PDF, JPEG, PNG, GIF, WebP, Word (.doc/.docx), Excel (.xls/.xlsx), plain text, CSV. Max size: 10 MB.

Response:

{
"key": "recruitment/attachments/<id>.pdf",
"name": "original-filename.pdf",
"size": 12345,
"type": "application/pdf"
}
GET /api/uploads/recruitment/attachments/:id

Returns the file inline with the original filename. Cached for 1 hour (Cache-Control: private, max-age=3600).

GET /api/uploads/recruitment/resumes/:id

Returns a resume file from R2 (default content type: application/pdf). Cached for 1 hour.


POST /api/uploads/files

Body: multipart/form-data with a file field.

Accepted types: PDF, images (JPEG, PNG, GIF, WebP, SVG), Office documents (Word, Excel, PowerPoint), plain text, CSV, Markdown, ZIP. Max size: 10 MB.

Response:

{
"url": "/api/uploads/files/<id>.pdf",
"key": "files/<id>.pdf",
"name": "original-filename.pdf",
"size": 12345,
"type": "application/pdf"
}
GET /api/uploads/files/:id

Returns the file inline with the original filename. Cached for 1 hour.