Confirm uploads
Uploads
Confirm uploads
Verify uploaded objects, validate MIME, and write an uploads row per file.
POST
Confirm uploads
Overview
Confirms that the client successfully PUT each presigned object to DigitalOcean Spaces. For everyfileKey the server:
- Verifies the key prefix matches the caller (
tenancy-docs/{userId}/...orlistings/{userId}/...) — otherwise403 FORBIDDEN. HEADs the object in storage to confirm presence and readContent-Length/Content-Type.- Cross-checks the stored
Content-Typeagainst the extension-implied MIME — if they disagree,400 VALIDATION_FAILED. (The full byte-level magic-bytes sniff lands with the Phase 7 image worker — until then the presign-time Content-Type pin and this cross-check are the spoof defence.) - Inserts a row into the
uploadscollection. - For
HOME_PHOTOkeys, enqueues animage.processBullMQ job (Phase 7 will run the worker — resize variants, EXIF GPS strip, blurhash). - Returns
{ uploadId, fileKey, cdnUrl }.cdnUrlisnullforTENANCY_DOC(private bucket; admins fetch via a separate signed-download endpoint).
uploadId is what onboarding and current-home endpoints reference (uploadId, photoUploadIds).
Authentication
Bearer <accessToken> required. Scope: user.
Path parameters
None.Query parameters
None.Request body
| Field | Type | Required | Allowed values / Constraints | Example |
|---|---|---|---|---|
fileKeys | string[] | yes | 1..10 entries. Each is a fileKey returned by POST /uploads/presign. 1..500 chars each. Must start with tenancy-docs/{callerUserId}/ or listings/{callerUserId}/. | ["listings/66400a8f1c2b4d5e6f7a8b00/01HZQ7K3M4N5P6Q7R8S9T0V1W2.jpg"] |
Example payload
Response — 200 OK
| Field | Type | Notes | Example |
|---|---|---|---|
files | object[] | One entry per input fileKey, same order. See ConfirmedFile object. |
ConfirmedFile object
| Field | Type | Allowed values | Example |
|---|---|---|---|
uploadId | string | 24-char MongoDB ObjectId. The id to pass to onboarding/current-home endpoints. | 66400a8f1c2b4d5e6f7a8b91 |
fileKey | string | The key echoed back. | listings/66400a8f1c2b4d5e6f7a8b00/01HZQ7K3M4N5P6Q7R8S9T0V1W2.jpg |
cdnUrl | string | null | Public CDN URL for HOME_PHOTO; null for TENANCY_DOC (private bucket). | https://cdn.swappr.co.uk/listings/66400a8f1c2b4d5e6f7a8b00/01HZQ7K3M4N5P6Q7R8S9T0V1W2.jpg |
Example response
Error responses
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | Body malformed; fileKeys empty or > 10; an object has an invalid size; the stored Content-Type does not match the extension-implied MIME (spoof attempt). |
| 401 | UNAUTHENTICATED | Missing, malformed, or expired access token. |
| 403 | FORBIDDEN | A fileKey does not start with tenancy-docs/{callerUserId}/ or listings/{callerUserId}/ — i.e. it belongs to a different user. |
| 404 | NOT_FOUND | The object was not found in storage (the client never completed the PUT, or the presign URL expired before upload). |
| 503 | STORAGE_NOT_CONFIGURED | DigitalOcean Spaces credentials are not provisioned in this environment (dev-only). |
Example error — 404
Side effects
- Inserts one row per
fileKeyinto theuploadscollection withuserId,fileType(inferred from the key prefix),mimeType(extension-derived),fileSizeBytes,fileKey, andurl(CDN URL forHOME_PHOTO, empty string forTENANCY_DOC). - For each
HOME_PHOTO, enqueues animage.processBullMQ job (Phase 2 records the call; Phase 7 runs the worker — resize, EXIF GPS strip, blurhash). - No write happens for any file in the batch if an earlier file fails — failures are surfaced in order.
See also
- Upload flow — full pipeline with sequence diagram.
- Presign upload URLs — the prior step.
- Step 1 — tenancy — consumes a
TENANCY_DOCupload. - Step 3 — photos — consumes
HOME_PHOTOuploads.