POST
/
api
/
v1
/
current-home
/
me
/
photos
Add listing photos
curl --request POST \
  --url https://api.example.com/api/v1/current-home/me/photos
import requests

url = "https://api.example.com/api/v1/current-home/me/photos"

response = requests.post(url)

print(response.text)
const options = {method: 'POST'};

fetch('https://api.example.com/api/v1/current-home/me/photos', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/current-home/me/photos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"net/http"
"io"
)

func main() {

url := "https://api.example.com/api/v1/current-home/me/photos"

req, _ := http.NewRequest("POST", url, nil)

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/current-home/me/photos")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.example.com/api/v1/current-home/me/photos")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)

response = http.request(request)
puts response.read_body

Overview

Attaches one or more previously-confirmed HOME_PHOTO uploads to the caller’s listing. The combined total (existing photos + the new batch) may not exceed 10. If the listing currently has no cover photo (i.e. this is the first batch attached outside the onboarding flow), the first photo in the batch is set as the cover. Each uploadId must already have been confirmed via POST /uploads/confirm and must belong to the caller — server cross-checks the uploads row by userId. This endpoint is reused after onboarding step 3 to add additional photos. During onboarding it is not used — step 3’s POST is the dedicated entry point.

Authentication

Bearer <accessToken> required. Scope: user.

Path parameters

None.

Query parameters

None.

Request body

FieldTypeRequiredAllowed values / ConstraintsExample
photoUploadIdsstring[]yes1..10 entries. Each is the uploadId returned from /uploads/confirm (24-char ObjectId).["66400a8f1c2b4d5e6f7a8d01"]
photoMetaobject[]noUp to 10 per-photo display-metadata entries computed client-side after cropping ({ uploadId, blurhash, width, height }). Keyed by uploadId; entries not referenced in photoUploadIds are ignored. When omitted, photos store an empty blurhash and 0×0 dimensions.see below

photoMeta[] object

FieldTypeRequiredConstraints
uploadIdstringyes24-char ObjectId; should match an entry in photoUploadIds.
blurhashstringyesBlurHash placeholder string, 1..120 chars. Painted by the app as a progressive placeholder until the full photo loads.
widthintegeryesCropped width in px, > 0.
heightintegeryesCropped height in px, > 0.

Example payload

{
  "photoUploadIds": [
    "66400a8f1c2b4d5e6f7a8d01",
    "66400a8f1c2b4d5e6f7a8d02"
  ],
  "photoMeta": [                                // optional — progressive placeholders
    {
      "uploadId": "66400a8f1c2b4d5e6f7a8d01",
      "blurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4",
      "width": 1600,
      "height": 1200
    }
  ]
}

Response — 200 OK

Returns the updated Home object including the appended photos.
{
  "home": {
    "id": "66400a8f1c2b4d5e6f7a8c00",
    "photos": [
      {
        "id": "66400a8f1c2b4d5e6f7a8e01",
        "url": "https://cdn.swappr.co.uk/listings/.../photo1.jpg",
        "thumbnailUrl": "https://cdn.swappr.co.uk/listings/.../photo1.jpg",
        "orderIndex": 0,
        "isCover": true,
        "fileSizeBytes": 2500000,
        "width": 1600,
        "height": 1200,
        "blurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4",
        "createdAt": "2026-05-22T14:32:08.412Z"
      }
      // ...up to 10 photos total
    ]
    // ...rest of the home shape
  }
}

Error responses

StatusCodeMeaning
400VALIDATION_FAILEDphotoUploadIds empty, more than 10 entries, or each id fails the ObjectId regex; OR the batch would push total over 10 photos.
401UNAUTHENTICATEDMissing, malformed, or expired access token.
404NOT_FOUNDThe user has no current_homes listing, or one of the uploadId values does not belong to the caller / does not exist / is not a HOME_PHOTO.

Example error — 400 (too many photos)

{
  "type": "https://api.swappr.co.uk/errors/validation-failed",
  "title": "Validation failed",
  "status": 400,
  "code": "VALIDATION_FAILED",
  "detail": "Adding 3 photo(s) would exceed the 10-photo cap (current: 9)",
  "instance": "/api/v1/current-home/me/photos",
  "requestId": "01HZQ7K3M4N5P6Q7R8S9T0V1W2"
}

Side effects

  • Appends new entries to current_homes.photos[] with orderIndex continuing from the current last + 1.
  • If the listing has zero existing photos AND no cover, the first photo in the batch is set isCover: true.
  • updatedAt bumped.
  • When photoMeta is supplied, each photo’s blurhash, width, and height are stored from it; otherwise they default to ""/0/0.
  • Enqueues an image.process job per new photo (stub in Phase 2; Phase 7 generates thumbnails, EXIF strip, and can backfill a server-computed blurhash when the client did not supply one).

See also

curl

curl -X POST https://api.swappr.co.uk/api/v1/current-home/me/photos \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "photoUploadIds": ["66400a8f1c2b4d5e6f7a8d01"] }'