Replace my preferences
Preferences
Replace my preferences
Full-replace the authenticated user’s match preferences. Triggers match.recompute.
PUT
Replace my preferences
Overview
Replaces the caller’suser_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.| Field | Type | Required | Allowed values / Constraints | Example |
|---|---|---|---|---|
desiredPropertyTypes | string[] | yes | Non-empty subset of: DETACHED, SEMI_DETACHED, TERRACED, FLAT, MAISONETTE, BUNGALOW. | ["FLAT", "MAISONETTE"] |
minBedrooms | integer | yes | 0..10 inclusive. Must be <= maxBedrooms. | 1 |
maxBedrooms | integer | yes | 0..10 inclusive. Must be >= minBedrooms. | 3 |
maxRent | object | yes | See MaxRent object. | |
preferredLocations | object[] | yes | 1..5 entries. See PreferredLocation object. | |
searchRadiusMiles | integer | yes | One of: 0, 1, 3, 5, 10, 25. (0 means “exact postcode only”.) | 5 |
desiredFeatures | string[] | yes | Possibly-empty subset of: GARDEN, PARKING, BALCONY, LIFT, GROUND_FLOOR, PETS_ALLOWED, WHEELCHAIR_ACCESS. Defaults to [] if omitted. | ["BALCONY"] |
MaxRent object
| Field | Type | Required | Notes / Constraints | Example |
|---|---|---|---|---|
amountMinor | integer | yes | Positive integer pence. | 150000 |
frequency | enum | yes | WEEKLY, MONTHLY | MONTHLY |
PreferredLocation object
Note: the wire format on the request body uses flatlng / lat fields (the controller maps them into GeoJSON Points server-side). The response body returns the stored GeoJSON shape — see Get my preferences.
| Field | Type | Required | Notes / Constraints | Example |
|---|---|---|---|---|
label | string | yes | 1..200 chars. Human-readable area name. | Camden, London |
lng | number | yes | -180..180. Decimal degrees, WGS-84. | -0.1426 |
lat | number | yes | -90..90. Decimal degrees, WGS-84. | 51.5390 |
postcode | string | null | yes | 1..16 chars, or null when the user selected an area rather than a postcode. | NW1 7JE |
Example payload
Response — 200 OK
Returns the updated Preferences object.maxRentMonthlyMinor is derived server-side and included.
Error responses
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | Any 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). |
| 401 | UNAUTHENTICATED | Missing, malformed, or expired access token. |
Example error — 400
Side effects
- Upserts the caller’s row in the
user_preferencescollection. maxRentMonthlyMinoris derived server-side frommaxRent(MONTHLY→ as-is;WEEKLY→round(amountMinor * 52 / 12)).updatedAtis bumped (or set tonowon insert).- Enqueues a
match.recomputejob with{ userId, reason: "preferences_updated" }(Phase 3 worker; stub in Phase 2).
See also
- Get my preferences
- Step 4 — desired home — first-time creation during onboarding.
- Update my current home — the inverse: editing the listing rather than the filter.