Files
The public API uses a presign + finalize flow for uploads. The API never touches your bytes directly — it only signs MinIO URLs and verifies the result. Required scope: files:write.
POST /v1/files/presign-upload
Reserves a file_id and returns a presigned MinIO PUT URL.
Request
curl -X POST \
-H "Authorization: Bearer ff_live_…" \
-H "Content-Type: application/json" \
-d '{
"filename": "budget.xlsx",
"size": 524288,
"mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}' \
https://fileforge-api.aldrickb.app/v1/files/presign-uploadResponse
{
"file_id": "9f6c...8e",
"bucket": "fileforge-inputs",
"storage_key": "<user_id>/2026/05/07/9f6c...8e-budget.xlsx",
"upload_url": "https://minio.lan/.../X-Amz-Signature=…",
"expires_in": 900
}The upload_url expires in 15 minutes. The signed URL carries the exact Content-Length you declared — uploading a different size will fail with a SigV4 mismatch from MinIO.
size is checked against your tier's max_file_mb. Free is 10 MB (and free can't use the API anyway); Pro is 500 MB; Enterprise is 2 GB.
PUT to upload_url
Use the URL exactly as returned, with the same Content-Type you declared:
curl --upload-file budget.xlsx \
-H "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
"$UPLOAD_URL"A successful PUT returns 200 with no body. Anything else is a fatal upload error — don't finalize it.
POST /v1/files/{id}/finalize
Verifies the object, runs magic-byte detection, and streams the file through ClamAV. On success, returns the persisted file record.
curl -X POST \
-H "Authorization: Bearer ff_live_…" \
-H "Content-Type: application/json" \
-d '{
"storage_key": "<user_id>/2026/05/07/9f6c...8e-budget.xlsx",
"original_name": "budget.xlsx",
"declared_mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}' \
https://fileforge-api.aldrickb.app/v1/files/9f6c...8e/finalizeSuccessful response (201):
{
"id": "9f6c...8e",
"original_name": "budget.xlsx",
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size_bytes": 524288,
"expires_at": "2026-05-14T12:34:56.000Z",
"created_at": "2026-05-07T12:34:56.000Z"
}If magic-byte detection disagrees with declared_mime, finalize returns 422 mime_mismatch and the object is deleted. If ClamAV finds something, finalize returns 422 malware_detected, the object is deleted, and a file.quarantined audit-log entry is written.
DELETE /v1/files/{id}
Wipes the object and the row. Required scope: files:delete.
curl -X DELETE \
-H "Authorization: Bearer ff_live_…" \
https://fileforge-api.aldrickb.app/v1/files/9f6c...8e204 No Content on success. 404 if the file ID doesn't belong to your account or has already been deleted.
