Presign upload URLs
Uploads
Presign upload URLs
Request presigned PUT URLs for direct-to-Spaces uploads (up to 10 files).
POST
Presign upload URLs
Overview
Issues short-lived presigned PUT URLs for direct browser/app uploads to DigitalOcean Spaces. The client never streams bytes through the API — files go directly to object storage using the returned URL and headers, then are confirmed viaPOST /uploads/confirm.
The request specifies a single fileType (TENANCY_DOC, HOME_PHOTO, or AVATAR) and a list of files, each with its mimeType and sizeBytes. The server validates per-file limits (see below), builds an object key of the form tenancy-docs/{userId}/{ulid}.{ext} (private bucket), listings/{userId}/{ulid}.{ext} (public bucket), or avatars/{userId}/{ulid}.{ext} (public bucket), and signs a PUT URL valid for 10 minutes.
Per-file validation:
TENANCY_DOC:mimeType∈ ;sizeBytes≤ 10,000,000.HOME_PHOTO:mimeType∈ ; 50,000 ≤sizeBytes≤ 10,000,000.AVATAR(profile photo):mimeType∈ ;sizeBytes≤ 10,000,000 (no minimum). After confirming, pass the returneduploadIdasavatarUploadIdtoPATCH /users/me.CHAT_MEDIA(chat attachment):mimeType∈ images , video , docs ;sizeBytes≤ 50,000,000 (no minimum). Public bucket, keychat-media/{userId}/{ulid}.{ext}. Post the returned CDN url back on a chat message’smedia.url— see send-message.
headers (Content-Type and any Content-Length-Range / signature headers) — DigitalOcean rejects the PUT otherwise.
See Upload flow for the full pipeline.
Authentication
Bearer <accessToken> required. Scope: user.
Path parameters
None.Query parameters
None.Request body
| Field | Type | Required | Allowed values / Constraints | Example |
|---|---|---|---|---|
fileType | enum | yes | TENANCY_DOC, HOME_PHOTO, AVATAR, CHAT_MEDIA | HOME_PHOTO |
files | object[] | yes | 1..10 entries. See File object. |
File object
| Field | Type | Required | Notes / Constraints | Example |
|---|---|---|---|---|
mimeType | string | yes | 1..100 chars. Allowlisted per fileType (see Overview). | image/jpeg |
sizeBytes | integer | yes | Positive integer. Allowlisted per fileType (see Overview). | 2_500_000 |
Example payload
Response — 200 OK
| Field | Type | Notes | Example |
|---|---|---|---|
files | object[] | One entry per input file, in the same order. See PresignedFile object. |
PresignedFile object
| Field | Type | Notes / Allowed values | Example |
|---|---|---|---|
uploadUrl | string | Presigned HTTPS PUT URL, valid for 10 minutes. | https://swappr-public.lon1.digitaloceanspaces.com/listings/...?X-Amz-Signature=... |
fileKey | string | Server-generated object key. Pass back to /uploads/confirm. | listings/66400a8f1c2b4d5e6f7a8b00/01HZQ7K3M4N5P6Q7R8S9T0V1W2.jpg |
headers | object | Required HTTP headers for the PUT. Keys are header names, values are strings. Must be sent verbatim. | { "Content-Type": "image/jpeg" } |
Example response
Error responses
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | Body malformed; files empty or > 10 entries; per-file MIME not allowed for the chosen fileType; sizeBytes not a positive integer or outside the allowed range. |
| 401 | UNAUTHENTICATED | Missing, malformed, or expired access token. |
| 503 | STORAGE_NOT_CONFIGURED | DigitalOcean Spaces credentials are not provisioned in this environment (dev-only). |
Example error — 400
Side effects
- No database writes yet — the
uploadsrow is created on confirm. - No bytes are streamed through the API; the response is purely cryptographic (SigV4 signature) plus the generated object key.
See also
- Upload flow — full pipeline with sequence diagram.
- Confirm uploads — the next step after the client PUTs to DigitalOcean.