PUT
/
api
/
v1
/
preferences
/
me
Replace my preferences
curl --request PUT \
  --url https://api.example.com/api/v1/preferences/me
import requests

url = "https://api.example.com/api/v1/preferences/me"

response = requests.put(url)

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

fetch('https://api.example.com/api/v1/preferences/me', 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/preferences/me",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
]);

$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/preferences/me"

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

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

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

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.put("https://api.example.com/api/v1/preferences/me")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.example.com/api/v1/preferences/me")

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

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

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

Overview

Replaces the caller’s user_preferences document. The semantics are a full PUT, not a PATCH — every field listed below must be supplied; omitted fields are not preserved. The server upserts: if the user already has a preferences document it is overwritten in place; if not (i.e. they reached this endpoint without going through onboarding step 4 — unlikely in normal flows) one is created. Either way the response is the updated document. The server derives maxRentMonthlyMinor from the supplied maxRent (weekly rents are annualised then divided by 12 and rounded to the nearest pence). The matching engine reads only maxRentMonthlyMinor, so weekly/monthly rents are directly comparable. After a successful write the server enqueues a match.recompute job with reason: "preferences_updated" so the matcher picks up the new filter (Phase 3 worker; stub in Phase 2 records the enqueue for ordering tests).

Authentication

Bearer <accessToken> required. Scope: user.

Path parameters

None.

Query parameters

None.

Request body

All fields are required.
FieldTypeRequiredAllowed values / ConstraintsExample
desiredPropertyTypesstring[]yesNon-empty subset of: DETACHED, SEMI_DETACHED, TERRACED, FLAT, MAISONETTE, BUNGALOW.["FLAT", "MAISONETTE"]
minBedroomsintegeryes0..10 inclusive. Must be <= maxBedrooms.1
maxBedroomsintegeryes0..10 inclusive. Must be >= minBedrooms.3
maxRentobjectyesSee MaxRent object.
preferredLocationsobject[]yes1..5 entries. See PreferredLocation object.
searchRadiusMilesintegeryesOne of: 0, 1, 3, 5, 10, 25. (0 means “exact postcode only”.)5
desiredFeaturesstring[]yesPossibly-empty subset of: GARDEN, PARKING, BALCONY, LIFT, GROUND_FLOOR, PETS_ALLOWED, WHEELCHAIR_ACCESS. Defaults to [] if omitted.["BALCONY"]

MaxRent object

FieldTypeRequiredNotes / ConstraintsExample
amountMinorintegeryesPositive integer pence.150000
frequencyenumyesWEEKLY, MONTHLYMONTHLY

PreferredLocation object

Note: the wire format on the request body uses flat lng / lat fields (the controller maps them into GeoJSON Points server-side). The response body returns the stored GeoJSON shape — see Get my preferences.
FieldTypeRequiredNotes / ConstraintsExample
labelstringyes1..200 chars. Human-readable area name.Camden, London
lngnumberyes-180..180. Decimal degrees, WGS-84.-0.1426
latnumberyes-90..90. Decimal degrees, WGS-84.51.5390
postcodestring | nullyes1..16 chars, or null when the user selected an area rather than a postcode.NW1 7JE

Example payload

{
  "desiredPropertyTypes": [
    "FLAT",                                            // enum (multi): "DETACHED" | "SEMI_DETACHED" | "TERRACED" | "FLAT" | "MAISONETTE" | "BUNGALOW"
    "MAISONETTE"
  ],
  "minBedrooms": 1,                                    // integer 0..10
  "maxBedrooms": 3,                                    // integer 0..10
  "maxRent": {
    "amountMinor": 150000,                             // integer pence; 150000 = £1,500.00
    "frequency": "MONTHLY"                             // enum: "WEEKLY" | "MONTHLY"
  },
  "preferredLocations": [
    {
      "label": "Camden, London",
      "lng": -0.1426,
      "lat": 51.5390,
      "postcode": "NW1 7JE"                            // string | null
    }
  ],
  "searchRadiusMiles": 5,                              // enum: 0 | 1 | 3 | 5 | 10 | 25
  "desiredFeatures": [
    "BALCONY"                                          // enum (multi): "GARDEN" | "PARKING" | "BALCONY" | "LIFT" | "GROUND_FLOOR" | "PETS_ALLOWED" | "WHEELCHAIR_ACCESS"
  ]
}

Response — 200 OK

Returns the updated Preferences object. maxRentMonthlyMinor is derived server-side and included.
{
  "preferences": {
    "id": "66400a8f1c2b4d5e6f7a8f00",
    "userId": "66400a8f1c2b4d5e6f7a8b00",
    "desiredPropertyTypes": ["FLAT", "MAISONETTE"],
    "minBedrooms": 1,
    "maxBedrooms": 3,
    "maxRent": { "amountMinor": 150000, "frequency": "MONTHLY" },
    "maxRentMonthlyMinor": 150000,
    "preferredLocations": [
      {
        "label": "Camden, London",
        "location": { "type": "Point", "coordinates": [-0.1426, 51.5390] },
        "postcode": "NW1 7JE"
      }
    ],
    "searchRadiusMiles": 5,
    "desiredFeatures": ["BALCONY"],
    "createdAt": "2026-05-22T14:30:00.123Z",
    "updatedAt": "2026-05-22T14:32:08.412Z"
  }
}

Error responses

StatusCodeMeaning
400VALIDATION_FAILEDAny field fails Zod (empty desiredPropertyTypes, preferredLocations outside 1..5, searchRadiusMiles not one of the allowed literals, amountMinor <= 0, or the minBedrooms <= maxBedrooms cross-field check).
401UNAUTHENTICATEDMissing, malformed, or expired access token.

Example error — 400

{
  "type": "https://api.swappr.co.uk/errors/validation-failed",
  "title": "Validation failed",
  "status": 400,
  "code": "VALIDATION_FAILED",
  "detail": "Request body failed validation",
  "instance": "/api/v1/preferences/me",
  "requestId": "01HZQ7K3M4N5P6Q7R8S9T0V1W2",
  "errors": [
    { "path": "minBedrooms", "message": "minBedrooms must be <= maxBedrooms", "code": "custom" }
  ]
}

Side effects

  • Upserts the caller’s row in the user_preferences collection.
  • maxRentMonthlyMinor is derived server-side from maxRent (MONTHLY → as-is; WEEKLYround(amountMinor * 52 / 12)).
  • updatedAt is bumped (or set to now on insert).
  • Enqueues a match.recompute job with { userId, reason: "preferences_updated" } (Phase 3 worker; stub in Phase 2).

See also

curl

curl -X PUT https://api.swappr.co.uk/api/v1/preferences/me \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "desiredPropertyTypes": ["FLAT", "MAISONETTE"],
    "minBedrooms": 1,
    "maxBedrooms": 3,
    "maxRent": { "amountMinor": 150000, "frequency": "MONTHLY" },
    "preferredLocations": [
      { "label": "Camden, London", "lng": -0.1426, "lat": 51.5390, "postcode": "NW1 7JE" }
    ],
    "searchRadiusMiles": 5,
    "desiredFeatures": ["BALCONY"]
  }'