/** * 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( endpoint: string, options: RequestInit = {} ): Promise { 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; }); } /** * Build query string from params object */ private buildQueryString(params: Record): 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> { const queryString = this.buildQueryString(params); return this.request>(`/properties/search${queryString}`); } async getProperty(propertyId: string): Promise { return this.request(`/properties/${propertyId}`); } async getPropertySummary(propertyId: string): Promise { return this.request(`/properties/${propertyId}/summary`); } // ==================== OWNERS ==================== async listPropertyOwners( propertyId: string, params?: PaginationParams ): Promise> { const queryString = this.buildQueryString(params || {}); return this.request>(`/properties/${propertyId}/owners${queryString}`); } async getOwner(ownerId: string): Promise { return this.request(`/owners/${ownerId}`); } async searchOwners(params: { name?: string; ownerType?: string; minProperties?: number; minValue?: number; } & PaginationParams): Promise> { const queryString = this.buildQueryString(params); return this.request>(`/owners/search${queryString}`); } // ==================== TENANTS ==================== async listPropertyTenants( propertyId: string, params?: PaginationParams ): Promise> { const queryString = this.buildQueryString(params || {}); return this.request>(`/properties/${propertyId}/tenants${queryString}`); } async getTenant(tenantId: string): Promise { return this.request(`/tenants/${tenantId}`); } async searchTenants(params: { name?: string; tenantType?: string; industry?: string; activeLeases?: boolean; } & PaginationParams): Promise> { const queryString = this.buildQueryString(params); return this.request>(`/tenants/search${queryString}`); } // ==================== TRANSACTIONS ==================== async listPropertyTransactions( propertyId: string, params?: { transactionType?: string } & PaginationParams ): Promise> { const queryString = this.buildQueryString(params || {}); return this.request>(`/properties/${propertyId}/transactions${queryString}`); } async getTransaction(transactionId: string): Promise { return this.request(`/transactions/${transactionId}`); } // ==================== MORTGAGES ==================== async listPropertyMortgages( propertyId: string, params?: { status?: string } & PaginationParams ): Promise> { const queryString = this.buildQueryString(params || {}); return this.request>(`/properties/${propertyId}/mortgages${queryString}`); } async getMortgage(mortgageId: string): Promise { return this.request(`/mortgages/${mortgageId}`); } // ==================== PERMITS ==================== async listBuildingPermits( propertyId: string, params?: { permitType?: string; status?: string } & PaginationParams ): Promise> { const queryString = this.buildQueryString(params || {}); return this.request>(`/properties/${propertyId}/permits${queryString}`); } async getPermit(permitId: string): Promise { return this.request(`/permits/${permitId}`); } // ==================== PROPERTY FINANCIALS ==================== async getPropertyFinancials(propertyId: string): Promise { return this.getProperty(propertyId); } async getPropertyTaxHistory(propertyId: string, years: number = 5): Promise { return this.getProperty(propertyId); } async searchOwnersByPortfolio(params: any): Promise { return this.searchOwners(params); } async getOwnerPortfolio(params: any): Promise { const { owner_id, ...rest } = params; return this.getOwner(owner_id); } async listNearbyProperties(params: any): Promise { return this.searchProperties(params); } async getPropertyComparables(params: any): Promise { return this.searchProperties(params); } async getZoningInfo(propertyId: string): Promise { return this.getProperty(propertyId); } async listRecentSales(params: any): Promise { return this.searchProperties(params); } }