GET
/
api
/
v1
/
admin
/
tenancy
/
{id}
Tenancy verification detail
curl --request GET \
  --url https://api.example.com/api/v1/admin/tenancy/{id}
import requests

url = "https://api.example.com/api/v1/admin/tenancy/{id}"

response = requests.get(url)

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

fetch('https://api.example.com/api/v1/admin/tenancy/{id}', 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/admin/tenancy/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);

$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/admin/tenancy/{id}"

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

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

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

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.get("https://api.example.com/api/v1/admin/tenancy/{id}")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.example.com/api/v1/admin/tenancy/{id}")

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

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

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

Overview

Everything a moderator needs to review one tenancy verification on a single page: the verification row (with a fresh 15-minute signed download URL for the private document), the applicant’s profile (including the decrypted dateOfBirth), their current-home listing (photos, description, features, rent, coordinates), and their desired-home preferences. GeoJSON [lng, lat] pairs are flattened to { lat, lng } server-side so clients can never swap them. Available to any admin role — the detail is read-only. The decisions (approve / reject) are role-gated.

Authentication

Bearer <accessToken> with scope: 'admin' required (requireAdmin).
Read-only — all admin roles (SUPER, MODERATOR, FINANCE) may view the detail. No audit row is written (parity with the queue listing).

Path parameters

NameTypeRequiredNotes
idstringyesTenancy verification ObjectId (from the queue).

Query parameters

None.

Request body

None.

Response — 200 OK

Four sections. applicant, currentHome, and preferences are independently nullable — a deleted account nulls applicant; a mid-onboarding user nulls currentHome/preferences.
FieldTypeNotes
verificationobjectThe verification row — same shape as a queue row, incl. documentDownloadUrl.
applicantobject | nullApplicant profile (below), or null if the account no longer exists.
currentHomeobject | nullThe home they have (below), or null while mid-onboarding.
preferencesobject | nullThe home they want (below), or null while mid-onboarding.

applicant

FieldTypeNotes
idstringUser id.
emailstringAccount email.
emailVerifiedboolean
firstName / lastNamestring | null
dateOfBirthstring | nullDate-only YYYY-MM-DD, decrypted at read from the PII store.
phoneE164string | nullDecrypted at read. Currently null for all users (no collection flow yet).
biostring | nullProfile bio.
avatarUrlstring | nullPublic CDN URL.
tenancyStatusenumNOT_SUBMITTED | PENDING | APPROVED | REJECTED.
onboardingStatusenumIN_PROGRESS | COMPLETE.
statusenumACTIVE | BANNED | SUSPENDED.
createdAt / lastSeenAtstring | nullISO timestamps.

currentHome

FieldTypeNotes
idstringListing id.
propertyTypeenumDETACHED | SEMI_DETACHED | TERRACED | FLAT | MAISONETTE | BUNGALOW.
bedrooms / bathroomsinteger0..10.
address / addressDetails / postcodestringUK-normalised postcode.
locationobject{ lat, lng } — flattened from GeoJSON.
rentobject{ amountMinor, frequency: WEEKLY | MONTHLY } in pence.
rentMonthlyMinorintegerMonthly-normalised pence.
featuresenum[]GARDEN, PARKING, BALCONY, LIFT, GROUND_FLOOR, PETS_ALLOWED, WHEELCHAIR_ACCESS.
photosarray{ url, thumbnailUrl, orderIndex, isCover, width, height, blurhash } — public CDN URLs, ordered, exactly one cover when non-empty.
descriptionstringEmpty in DRAFT, else 100–150 chars.
statusenumDRAFT | LIVE | HIDDEN.
createdAt / updatedAtstring | nullISO timestamps.

preferences

FieldTypeNotes
idstringPreferences row id.
desiredPropertyTypesenum[]Same enum as propertyType, ≥1 entry.
minBedrooms / maxBedroomsintegermin ≤ max.
maxRent / maxRentMonthlyMinorobject / integerSame shapes as rent.
preferredLocationsarray1–5 × { label, lat, lng, postcode | null }.
searchRadiusMilesnumberOne of 0, 1, 3, 5, 10, 25 (0 = exact area only).
desiredFeaturesenum[]Same enum as features.
createdAt / updatedAtstring | nullISO timestamps.
{
  "verification": {
    "id": "66400a8f1c2b4d5e6f7ab000",
    "userId": "66400a8f1c2b4d5e6f7a8b01",
    "landlordName": "Acme Lettings",
    "documentType": "TENANCY_AGREEMENT",
    "uploadId": "66400a8f1c2b4d5e6f7ac000",
    "reviewStatus": "PENDING",
    "reviewedBy": null,
    "reviewedAt": null,
    "reviewNotes": null,
    "createdAt": "2026-05-20T08:30:00.000Z",
    "user": { "id": "66400a8f1c2b4d5e6f7a8b01", "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe" },
    "upload": { "id": "66400a8f1c2b4d5e6f7ac000", "fileKey": "tenancy-docs/66400a8f1c2b4d5e6f7a8b01/agreement.pdf", "mimeType": "application/pdf", "fileSizeBytes": 284913 },
    "documentDownloadUrl": "https://…?X-Amz-Expires=900&…"
  },
  "applicant": {
    "id": "66400a8f1c2b4d5e6f7a8b01",
    "email": "jane@example.com",
    "emailVerified": true,
    "firstName": "Jane",
    "lastName": "Doe",
    "dateOfBirth": "1990-04-15",
    "phoneE164": null,
    "bio": "Looking to move closer to family.",
    "avatarUrl": "https://cdn.swappr.co.uk/avatars/jane.jpg",
    "tenancyStatus": "PENDING",
    "onboardingStatus": "COMPLETE",
    "status": "ACTIVE",
    "createdAt": "2026-01-01T00:00:00.000Z",
    "lastSeenAt": "2026-05-19T21:14:00.000Z"
  },
  "currentHome": {
    "id": "66400a8f1c2b4d5e6f7ad000",
    "propertyType": "FLAT",
    "bedrooms": 2,
    "bathrooms": 1,
    "address": "10 Example Street, London",
    "addressDetails": "Flat 2, second floor",
    "postcode": "SW1A 1AA",
    "location": { "lat": 51.5034, "lng": -0.1276 },
    "rent": { "amountMinor": 120000, "frequency": "MONTHLY" },
    "rentMonthlyMinor": 120000,
    "features": ["GARDEN", "PARKING"],
    "photos": [
      { "url": "https://cdn.swappr.co.uk/homes/p1.jpg", "thumbnailUrl": "https://cdn.swappr.co.uk/homes/p1-thumb.jpg", "orderIndex": 0, "isCover": true, "width": 1200, "height": 800, "blurhash": "LEHV6nWB2yk8" }
    ],
    "description": "Bright two-bed flat close to transport…",
    "status": "LIVE",
    "createdAt": "2026-02-01T00:00:00.000Z",
    "updatedAt": "2026-03-01T00:00:00.000Z"
  },
  "preferences": {
    "id": "66400a8f1c2b4d5e6f7ae000",
    "desiredPropertyTypes": ["FLAT", "TERRACED"],
    "minBedrooms": 1,
    "maxBedrooms": 3,
    "maxRent": { "amountMinor": 150000, "frequency": "MONTHLY" },
    "maxRentMonthlyMinor": 150000,
    "preferredLocations": [
      { "label": "Camden", "lat": 51.539, "lng": -0.1426, "postcode": "NW1 0LT" }
    ],
    "searchRadiusMiles": 5,
    "desiredFeatures": ["PARKING"],
    "createdAt": "2026-02-01T00:00:00.000Z",
    "updatedAt": "2026-03-01T00:00:00.000Z"
  }
}
verification.documentDownloadUrl expires 15 minutes after the response is generated. If the moderator keeps the page open longer, re-fetch the detail to mint a new one.

Error responses

StatusCodeMeaning
401UNAUTHENTICATEDMissing, malformed, expired, or non-admin-scope token.
404NOT_FOUNDUnknown or malformed verification id.

See also

curl

curl "https://api.swappr.co.uk/api/v1/admin/tenancy/66400a8f1c2b4d5e6f7ab000" \
  -H "Authorization: Bearer $ADMIN_ACCESS_TOKEN"

Postman

See docs/postman/swappr.postman_collection.jsonAdmin Tenancy → Detail.