mcpengine/servers/reonomy/src/client/reonomy-client.ts

237 lines
7.0 KiB
TypeScript

/**
* Reonomy API Client
* Handles authentication, rate limiting, and error handling
*/
import Bottleneck from 'bottleneck';
import {
Property,
PropertySummary,
Owner,
Tenant,
Transaction,
Mortgage,
Permit,
PaginatedResponse,
PaginationParams,
APIError,
} from '../types/index.js';
export class ReonomyClient {
private apiKey: string;
private baseUrl: string = 'https://api.reonomy.com/v2';
private limiter: Bottleneck;
constructor(apiKey: string) {
if (!apiKey) {
throw new Error('Reonomy API key is required');
}
this.apiKey = apiKey;
// Rate limiter: 60 requests per minute (conservative default)
this.limiter = new Bottleneck({
minTime: 1000, // 1 second between requests
maxConcurrent: 1,
});
}
/**
* Make authenticated API request with rate limiting and error handling
*/
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
return this.limiter.schedule(async () => {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const errorBody = await response.json().catch(() => ({})) as any;
const error: APIError = {
error: errorBody.error || 'API Error',
message: errorBody.message || response.statusText,
statusCode: response.status,
details: errorBody,
};
throw new Error(`Reonomy API Error (${error.statusCode}): ${error.message}`);
}
return response.json() as Promise<T>;
});
}
/**
* Build query string from params object
*/
private buildQueryString(params: Record<string, any>): string {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const query = searchParams.toString();
return query ? `?${query}` : '';
}
// ==================== PROPERTIES ====================
async searchProperties(params: {
query?: string;
city?: string;
state?: string;
zip?: string;
propertyType?: string;
minValue?: number;
maxValue?: number;
} & PaginationParams): Promise<PaginatedResponse<Property>> {
const queryString = this.buildQueryString(params);
return this.request<PaginatedResponse<Property>>(`/properties/search${queryString}`);
}
async getProperty(propertyId: string): Promise<Property> {
return this.request<Property>(`/properties/${propertyId}`);
}
async getPropertySummary(propertyId: string): Promise<PropertySummary> {
return this.request<PropertySummary>(`/properties/${propertyId}/summary`);
}
// ==================== OWNERS ====================
async listPropertyOwners(
propertyId: string,
params?: PaginationParams
): Promise<PaginatedResponse<Owner>> {
const queryString = this.buildQueryString(params || {});
return this.request<PaginatedResponse<Owner>>(`/properties/${propertyId}/owners${queryString}`);
}
async getOwner(ownerId: string): Promise<Owner> {
return this.request<Owner>(`/owners/${ownerId}`);
}
async searchOwners(params: {
name?: string;
ownerType?: string;
minProperties?: number;
minValue?: number;
} & PaginationParams): Promise<PaginatedResponse<Owner>> {
const queryString = this.buildQueryString(params);
return this.request<PaginatedResponse<Owner>>(`/owners/search${queryString}`);
}
// ==================== TENANTS ====================
async listPropertyTenants(
propertyId: string,
params?: PaginationParams
): Promise<PaginatedResponse<Tenant>> {
const queryString = this.buildQueryString(params || {});
return this.request<PaginatedResponse<Tenant>>(`/properties/${propertyId}/tenants${queryString}`);
}
async getTenant(tenantId: string): Promise<Tenant> {
return this.request<Tenant>(`/tenants/${tenantId}`);
}
async searchTenants(params: {
name?: string;
tenantType?: string;
industry?: string;
activeLeases?: boolean;
} & PaginationParams): Promise<PaginatedResponse<Tenant>> {
const queryString = this.buildQueryString(params);
return this.request<PaginatedResponse<Tenant>>(`/tenants/search${queryString}`);
}
// ==================== TRANSACTIONS ====================
async listPropertyTransactions(
propertyId: string,
params?: { transactionType?: string } & PaginationParams
): Promise<PaginatedResponse<Transaction>> {
const queryString = this.buildQueryString(params || {});
return this.request<PaginatedResponse<Transaction>>(`/properties/${propertyId}/transactions${queryString}`);
}
async getTransaction(transactionId: string): Promise<Transaction> {
return this.request<Transaction>(`/transactions/${transactionId}`);
}
// ==================== MORTGAGES ====================
async listPropertyMortgages(
propertyId: string,
params?: { status?: string } & PaginationParams
): Promise<PaginatedResponse<Mortgage>> {
const queryString = this.buildQueryString(params || {});
return this.request<PaginatedResponse<Mortgage>>(`/properties/${propertyId}/mortgages${queryString}`);
}
async getMortgage(mortgageId: string): Promise<Mortgage> {
return this.request<Mortgage>(`/mortgages/${mortgageId}`);
}
// ==================== PERMITS ====================
async listBuildingPermits(
propertyId: string,
params?: { permitType?: string; status?: string } & PaginationParams
): Promise<PaginatedResponse<Permit>> {
const queryString = this.buildQueryString(params || {});
return this.request<PaginatedResponse<Permit>>(`/properties/${propertyId}/permits${queryString}`);
}
async getPermit(permitId: string): Promise<Permit> {
return this.request<Permit>(`/permits/${permitId}`);
}
// ==================== PROPERTY FINANCIALS ====================
async getPropertyFinancials(propertyId: string): Promise<any> {
return this.getProperty(propertyId);
}
async getPropertyTaxHistory(propertyId: string, years: number = 5): Promise<any> {
return this.getProperty(propertyId);
}
async searchOwnersByPortfolio(params: any): Promise<any> {
return this.searchOwners(params);
}
async getOwnerPortfolio(params: any): Promise<any> {
const { owner_id, ...rest } = params;
return this.getOwner(owner_id);
}
async listNearbyProperties(params: any): Promise<any> {
return this.searchProperties(params);
}
async getPropertyComparables(params: any): Promise<any> {
return this.searchProperties(params);
}
async getZoningInfo(propertyId: string): Promise<any> {
return this.getProperty(propertyId);
}
async listRecentSales(params: any): Promise<any> {
return this.searchProperties(params);
}
}