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.
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
- TypeScript
- Python
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);
import os
from wiil import WiilClient
from wiil.models.business_mgt import CreateProperty, CreatePropertyAddress, CreatePropertyCategory
client = WiilClient(api_key=os.environ["WIIL_API_KEY"])
property_config = client.property_config
property_inquiry = client.property_inquiry
category = property_config.create_category(
CreatePropertyCategory(name="Luxury Homes", description="High-end residential listings", property_type="residential", display_order=1)
)
address = property_config.create_address(
CreatePropertyAddress(street="123 Ocean View Drive", city="Miami", state="FL", postal_code="33139", country="USA")
)
property_listing = property_config.create(
CreateProperty(
category_id=category.id,
address_id=address.id,
title="Stunning Oceanfront Villa",
description="Luxury 5-bedroom villa with panoramic ocean views",
property_type="residential",
property_sub_type="villa",
listing_type="sale",
listing_status="active",
sale_price=2500000,
sale_price_currency="USD",
is_active=True,
)
)
print("Property created:", property_listing.id)
Property categories
Create, get, list, update
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import CreatePropertyCategory, UpdatePropertyCategory
from wiil.types import PaginationRequest
category = property_config.create_category(
CreatePropertyCategory(name="Commercial", property_type="commercial")
)
loaded = property_config.get_category(category.id)
categories = property_config.list_categories(PaginationRequest(page=1, page_size=20))
updated = property_config.update_category(
UpdatePropertyCategory(id=category.id, name="Premium Commercial")
)
Category deletion is exposed through the TypeScript SDK: await client.propertyConfig.deleteCategory('cat_123').
Batch create categories
Up to 50 categories per request.
- TypeScript
- Python
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 },
]);
from wiil.models.business_mgt import CreatePropertyCategory
categories = property_config.create_category_batch([
CreatePropertyCategory(name="Apartments", property_type="residential", display_order=1),
CreatePropertyCategory(name="Houses", property_type="residential", display_order=2),
CreatePropertyCategory(name="Commercial", property_type="commercial", display_order=3),
])
Property addresses
An address is a standalone, verifiable entity referenced by a property's addressId.
Create, verify, update
- TypeScript
- Python
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',
});
from wiil.models.business_mgt import CreatePropertyAddress, UpdatePropertyAddress
address = property_config.create_address(
CreatePropertyAddress(street="456 Park Avenue", city="New York", state="NY", postal_code="10022", country="USA", is_verified=False)
)
address = property_config.verify_address(address.id)
address = property_config.update_address(
UpdatePropertyAddress(id=address.id, neighborhood="Midtown East")
)
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.
- TypeScript
- Python
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' },
]);
from wiil.models.business_mgt import CreatePropertyAddress
addresses = property_config.create_address_batch([
CreatePropertyAddress(street="123 Main St", city="New York", state="NY", postal_code="10001", country="US"),
CreatePropertyAddress(street="456 Oak Ave", city="Los Angeles", state="CA", postal_code="90001", country="US"),
])
Properties
Create a listing
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import CreateProperty
prop = property_config.create(
CreateProperty(
category_id="cat_123",
address_id="addr_123",
title="Downtown Office Suite",
property_type="commercial",
property_sub_type="office",
listing_type="rent",
rental_price=4500,
rental_period="monthly",
)
)
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.
Get, list, by category, search
- TypeScript
- Python
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 });
from wiil.types import PaginationRequest
loaded = property_config.get("prop_123")
all_properties = property_config.list(PaginationRequest(page=1, page_size=20))
category_properties = property_config.get_by_category("cat_123", PaginationRequest(page=1, page_size=20))
search_results = property_config.search("office", PaginationRequest(page=1, page_size=20))
Looking a property up by its address is exposed through the TypeScript SDK: await client.propertyConfig.getByAddress('addr_123').
Update and delete
- TypeScript
- Python
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');
from wiil.models.business_mgt import UpdateProperty
updated = property_config.update(
UpdateProperty(id="prop_123", listing_status="under_offer", is_featured=True)
)
property_config.delete("prop_123")
Batch create properties
Up to 50 properties per request.
- TypeScript
- Python
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,
},
]);
from wiil.models.business_mgt import CreateProperty
properties = property_config.create_batch([
CreateProperty(title="Downtown Loft", description="Modern 2BR loft", category_id="cat_apartments", address_id="addr_nyc", property_type="residential", listing_type="rent", rental_price=2500, rental_period="monthly"),
CreateProperty(title="Beach House", description="3BR oceanfront", category_id="cat_houses", address_id="addr_miami", property_type="residential", listing_type="sale", sale_price=750000, sale_price_currency="USD"),
])
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.
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import CreatePropertyInquiry
inquiry = property_inquiry.create(
CreatePropertyInquiry(
property_id="property_123",
customer_id="cust_456",
message="Is this property still available?",
)
)
Get by property, update, update status
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import UpdatePropertyInquiry
from wiil.types import PaginationRequest
by_property = property_inquiry.get_by_property("property_123", PaginationRequest(page=1, page_size=20))
updated = property_inquiry.update(
UpdatePropertyInquiry(id="inq_123", notes="Customer requested weekend viewing")
)
status_updated = property_inquiry.update_status("inq_123", "in_progress")
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.
- TypeScript
- Python
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,
});
}
from wiil.models.business_mgt import CreatePropertyInquiry, UpdatePropertyInquiryStatus
from wiil.models.type_definitions import PropertyInquiryType, PropertyInquiryStatus
slots = property_inquiry.get_viewing_slots(property_id="property_123", local_date="2026-06-25")
print(slots.timezone, slots.local_date)
if slots.slots:
slot = slots.slots[0] # start_time_of_day, provider_id, start_time_utc_sec
inquiry = property_inquiry.create(
CreatePropertyInquiry(
property_id="property_123",
customer_id="cust_456",
inquiry_type=PropertyInquiryType.GENERAL,
message="I'd like to schedule a viewing",
scheduled_viewing_date=slot.start_time_utc_sec,
assigned_agent_id=slot.provider_id,
)
)
property_inquiry.update_status(
inquiry.id,
UpdatePropertyInquiryStatus(
id=inquiry.id,
status=PropertyInquiryStatus.VIEWING_SCHEDULED,
scheduled_viewing_date=slot.start_time_utc_sec,
),
)
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
warehouseis commercial, not residential. - Keep listing status current —
draft → 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
- Catalog reference: Categories & Addresses, Properties, Inquiries
- Notifications: lead follow-ups via outbound communications