Skip to main content

Property Management Guide

Manage real-estate listings, addresses, and customer inquiries.

This guide covers property categories, verifiable addresses, listings with type-specific pricing, and the lead lifecycle.

SDK conventions

TypeScript examples use client.propertyConfig (listings, categories, addresses) and client.propertyInquiries; enums import from wiil-js. Python accesses the property resources from the client:

property_config = client.property_config
property_inquiry = client.property_inquiry

Bilingual tabs are shown where both SDKs cover an operation; a few operations (category delete, address get/list/delete, inquiry get/get-by-customer/list/delete, get-by-address) are exposed through the TypeScript SDK and are labeled.

Quick start

import { WiilClient, PropertyType, PropertySubType, ListingType, ListingStatus } from 'wiil-js';

const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });

// 1. Category
const category = await client.propertyConfig.createCategory({
name: 'Luxury Homes',
propertyType: PropertyType.RESIDENTIAL,
isDefault: false,
});

// 2. Address (verify before listing)
const address = await client.propertyConfig.createAddress({
street: '123 Ocean View Drive',
city: 'Miami',
state: 'FL',
postalCode: '33139',
country: 'USA',
isVerified: false,
});

// 3. Listing
const property = await client.propertyConfig.create({
categoryId: category.id,
addressId: address.id,
title: 'Stunning Oceanfront Villa',
description: 'Luxury 5-bedroom villa with panoramic ocean views',
propertyType: PropertyType.RESIDENTIAL,
propertySubType: PropertySubType.VILLA,
listingType: ListingType.SALE,
listingStatus: ListingStatus.ACTIVE,
salePrice: 2500000,
salePriceCurrency: 'USD',
features: { bedrooms: 5, bathrooms: 4.5, squareFootage: 4500, amenities: ['Pool', 'Ocean view'] },
isActive: true,
});

console.log('Property created:', property.id);

Property categories

Create, get, list, update

import { PropertyType } from 'wiil-js';

const category = await client.propertyConfig.createCategory({
name: 'Waterfront Properties',
description: 'Properties with water access or views',
propertyType: PropertyType.RESIDENTIAL,
displayOrder: 1,
isDefault: false,
});

const loaded = await client.propertyConfig.getCategory('cat_123');
const result = await client.propertyConfig.listCategories({ page: 1, pageSize: 20 });

const updated = await client.propertyConfig.updateCategory({
id: 'cat_123',
name: 'Premium Waterfront Properties',
displayOrder: 0,
});

Category deletion is exposed through the TypeScript SDK: await client.propertyConfig.deleteCategory('cat_123').

Batch create categories

Up to 50 categories per request.

import { PropertyType } from 'wiil-js';

const categories = await client.propertyConfig.createCategoryBatch([
{ name: 'Luxury Homes', propertyType: PropertyType.RESIDENTIAL, displayOrder: 1 },
{ name: 'Waterfront Properties', propertyType: PropertyType.RESIDENTIAL, displayOrder: 2 },
{ name: 'Commercial Offices', propertyType: PropertyType.COMMERCIAL, displayOrder: 3 },
]);

Property addresses

An address is a standalone, verifiable entity referenced by a property's addressId.

Create, verify, update

const address = await client.propertyConfig.createAddress({
street: '456 Park Avenue',
unit: 'PH-1',
city: 'New York',
state: 'NY',
postalCode: '10022',
country: 'USA',
neighborhood: 'Midtown East',
coordinates: { latitude: 40.7614, longitude: -73.9705 },
isVerified: false,
});

const verified = await client.propertyConfig.verifyAddress('addr_123');

const updated = await client.propertyConfig.updateAddress({
id: 'addr_123',
unit: 'Suite 100',
neighborhood: 'Financial District',
});

Address reads and deletion are exposed through the TypeScript SDK:

const address = await client.propertyConfig.getAddress('addr_123');
const result = await client.propertyConfig.listAddresses({ page: 1, pageSize: 20 });
await client.propertyConfig.deleteAddress('addr_123');

Batch create addresses

Up to 50 addresses per request.

const addresses = await client.propertyConfig.createAddressBatch([
{ street: '123 Ocean Drive', city: 'Miami', state: 'FL', postalCode: '33139', country: 'USA' },
{ street: '456 Park Avenue', city: 'New York', state: 'NY', postalCode: '10022', country: 'USA' },
]);

Properties

Create a listing

import { PropertyType, PropertySubType, ListingType, ListingStatus, PropertyCondition } from 'wiil-js';

const property = await client.propertyConfig.create({
categoryId: 'cat_luxury',
addressId: 'addr_123',
title: 'Modern Downtown Condo',
description: 'Sleek 2BR condo with city skyline views',
propertyType: PropertyType.RESIDENTIAL,
propertySubType: PropertySubType.CONDO,
listingType: ListingType.SALE,
listingStatus: ListingStatus.ACTIVE,
salePrice: 750000,
salePriceCurrency: 'USD',
priceNegotiable: true,
features: {
bedrooms: 2,
bathrooms: 2,
parkingSpaces: 1,
squareFootage: 1200,
amenities: ['Gym', 'Pool', 'Concierge'],
utilities: ['Electric', 'Gas', 'Water'],
},
condition: PropertyCondition.NEW,
furnished: false,
images: ['https://example.com/living-room.jpg'],
isActive: true,
isFeatured: true,
});

For a rental, set listingType to rent, a rentalPrice, and a rentalPeriod (daily, weekly, monthly, yearly); for a listing that is both, use both with both prices.

const property = await client.propertyConfig.get('prop_123');
const result = await client.propertyConfig.list({ page: 1, pageSize: 20, includeDeleted: false });
const inCategory = await client.propertyConfig.getByCategory('cat_luxury', { page: 1, pageSize: 20 });
const found = await client.propertyConfig.search('oceanfront villa', { page: 1, pageSize: 10 });

Looking a property up by its address is exposed through the TypeScript SDK: await client.propertyConfig.getByAddress('addr_123').

Update and delete

import { ListingStatus } from 'wiil-js';

const updated = await client.propertyConfig.update({
id: 'prop_123',
salePrice: 725000,
listingStatus: ListingStatus.UNDER_OFFER,
priceNegotiable: false,
});

await client.propertyConfig.delete('prop_123');

Batch create properties

Up to 50 properties per request.

import { PropertyType, PropertySubType, ListingType } from 'wiil-js';

const properties = await client.propertyConfig.createBatch([
{
categoryId: 'cat_luxury',
addressId: 'addr_001',
title: 'Oceanfront Villa',
propertyType: PropertyType.RESIDENTIAL,
propertySubType: PropertySubType.VILLA,
listingType: ListingType.SALE,
salePrice: 2500000,
features: { bedrooms: 5, bathrooms: 4, squareFootage: 4500 },
isActive: true,
},
]);

Enums

import { PropertyType, PropertySubType, ListingType, ListingStatus, RentalPeriod, PropertyCondition } from 'wiil-js';

// PropertyType: 'residential' | 'commercial' | 'land'
// PropertySubType: house, apartment, condo, townhouse, villa, office, retail, warehouse, industrial, lot, farm, acreage
// ListingType: 'sale' | 'rent' | 'both'
// ListingStatus: 'draft' | 'active' | 'under_offer' | 'sold' | 'leased' | 'withdrawn'
// RentalPeriod: 'daily' | 'weekly' | 'monthly' | 'yearly'
// PropertyCondition: 'new' | 'excellent' | 'good' | 'fair' | 'needs_work'

Property inquiries

Create an inquiry

The TypeScript SDK captures the contact inline; the Python SDK links to an existing customer_id.

import { PropertyInquiryType, PreferredContactMethod } from 'wiil-js';

const inquiry = await client.propertyInquiries.create({
propertyId: 'prop_456',
customer: {
firstName: 'John',
lastName: 'Smith',
email: 'john.smith@example.com',
phone: '+1-555-123-4567',
preferredContactMethod: PreferredContactMethod.EMAIL,
},
inquiryType: PropertyInquiryType.GENERAL,
message: 'I am interested in scheduling a viewing.',
source: 'website',
preferredViewingDate: Date.now() + 3 * 24 * 60 * 60 * 1000,
preferredViewingTime: '10:00 AM',
interestedInBuying: true,
budgetMin: 500000,
budgetMax: 800000,
});

Get by property, update, update status

import { PropertyInquiryStatus } from 'wiil-js';

const byProperty = await client.propertyInquiries.getByProperty('prop_456', { page: 1, pageSize: 20 });

const updated = await client.propertyInquiries.update({
id: 'inq_123',
assignedAgentId: 'agent_456',
scheduledViewingDate: Date.now() + 2 * 24 * 60 * 60 * 1000,
notes: 'Client prefers morning viewings',
});

const scheduled = await client.propertyInquiries.updateStatus('inq_123', {
id: 'inq_123',
status: PropertyInquiryStatus.VIEWING_SCHEDULED,
scheduledViewingDate: Date.now() + 2 * 24 * 60 * 60 * 1000,
});

Reading a single inquiry, fetching a customer's inquiries, listing, and deletion are exposed through the TypeScript SDK:

const inquiry = await client.propertyInquiries.get('inq_123');
const byCustomer = await client.propertyInquiries.getByCustomer('cust_123', { page: 1, pageSize: 20 });
const all = await client.propertyInquiries.list({ page: 1, pageSize: 20 });
await client.propertyInquiries.delete('inq_123');

Viewing slots

Query a property's available viewing slots for a date, then schedule a viewing using the slot's UTC timestamp. Slot times are in UTC seconds — pass startTimeUtcSec directly to the API; only multiply by 1000 when converting to a JavaScript Date for display.

import { PropertyInquiryType, PropertyInquiryStatus } from 'wiil-js';

const slots = await client.propertyInquiries.getViewingSlots('prop_123', '2026-06-25');
console.log(slots.timezone, slots.localDate);

if (slots.slots.length > 0) {
const slot = slots.slots[0]; // startTimeOfDay, providerId, startTimeUtcSec

const inquiry = await client.propertyInquiries.create({
propertyId: 'prop_123',
customerId: 'cust_456',
inquiryType: PropertyInquiryType.GENERAL,
message: "I'd like to schedule a viewing",
scheduledViewingDate: slot.startTimeUtcSec,
assignedAgentId: slot.providerId,
});

await client.propertyInquiries.updateStatus(inquiry.id, {
id: inquiry.id,
status: PropertyInquiryStatus.VIEWING_SCHEDULED,
scheduledViewingDate: slot.startTimeUtcSec,
});
}

Each slot carries startTimeOfDay, providerId, and startTimeUtcSec; the response also includes timezone and the queried localDate.

Convert a lead

import { PropertyInquiryStatus } from 'wiil-js';

await client.propertyInquiries.update({
id: 'inq_123',
convertedToTransaction: true,
transactionId: 'txn_789',
transactionType: 'purchase',
});

await client.propertyInquiries.updateStatus('inq_123', {
id: 'inq_123',
status: PropertyInquiryStatus.CONVERTED,
});

Inquiry enums

import { PropertyInquiryType, PropertyInquiryStatus } from 'wiil-js';

// PropertyInquiryType: 'offer' | 'general'
// PropertyInquiryStatus: 'new' | 'contacted' | 'viewing_scheduled' | 'follow_up' | 'converted' | 'closed'

Complete example: agency workflow

import {
WiilClient,
PropertyType,
PropertySubType,
ListingType,
ListingStatus,
PropertyInquiryType,
PropertyInquiryStatus,
PreferredContactMethod,
} from 'wiil-js';

async function realEstateWorkflow() {
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });

// 1. Category
const category = await client.propertyConfig.createCategory({
name: 'Luxury Homes',
description: 'Premium properties over $1M',
propertyType: PropertyType.RESIDENTIAL,
displayOrder: 1,
isDefault: false,
});

// 2. Address, then verify
const address = await client.propertyConfig.createAddress({
street: '100 Ocean Drive',
city: 'Palm Beach',
state: 'FL',
postalCode: '33480',
country: 'USA',
coordinates: { latitude: 26.7056, longitude: -80.0364 },
isVerified: false,
});
await client.propertyConfig.verifyAddress(address.id);

// 3. Listing
const property = await client.propertyConfig.create({
categoryId: category.id,
addressId: address.id,
title: 'Oceanfront Estate with Private Beach',
description: 'Magnificent estate with 200 feet of ocean frontage',
propertyType: PropertyType.RESIDENTIAL,
propertySubType: PropertySubType.VILLA,
listingType: ListingType.SALE,
listingStatus: ListingStatus.ACTIVE,
salePrice: 12500000,
salePriceCurrency: 'USD',
features: { bedrooms: 8, bathrooms: 10, squareFootage: 15000, amenities: ['Private beach', 'Infinity pool'] },
images: ['https://example.com/estate.jpg'],
isActive: true,
isFeatured: true,
});

// 4. Inquiry, worked through the pipeline
const inquiry = await client.propertyInquiries.create({
propertyId: property.id,
customer: {
firstName: 'Jane',
lastName: 'Doe',
email: 'jane@example.com',
phone: '+15550000001',
preferredContactMethod: PreferredContactMethod.PHONE,
},
inquiryType: PropertyInquiryType.GENERAL,
message: 'Interested in a private showing.',
source: 'website',
interestedInBuying: true,
budgetMax: 13000000,
});

await client.propertyInquiries.updateStatus(inquiry.id, { id: inquiry.id, status: PropertyInquiryStatus.CONTACTED });
await client.propertyInquiries.updateStatus(inquiry.id, {
id: inquiry.id,
status: PropertyInquiryStatus.VIEWING_SCHEDULED,
scheduledViewingDate: Date.now() + 5 * 24 * 60 * 60 * 1000,
});

// 5. Convert and close
await client.propertyInquiries.update({
id: inquiry.id,
convertedToTransaction: true,
transactionId: 'txn_estate_001',
transactionType: 'purchase',
});
await client.propertyInquiries.updateStatus(inquiry.id, { id: inquiry.id, status: PropertyInquiryStatus.CONVERTED });
await client.propertyConfig.update({ id: property.id, listingStatus: ListingStatus.SOLD, isActive: false });

return { category, address, property, inquiry };
}

realEstateWorkflow().catch(console.error);

Best practices

  • Verify addresses before listing — an unverified address can block a listing from going active.
  • Match subtype to type — a warehouse is commercial, not residential.
  • Keep listing status currentdraft → active → under_offer → sold/leased (any status → withdrawn).
  • Work the lead pipeline — assign an agent promptly, capture budgetMin/budgetMax, record viewing feedback, and convert when deals close.

Next steps


← Back to Guides