Skip to content

SDK Quickstart

Terminal window
npm install @borough/sdk
import { BoroughClient } from "@borough/sdk";
const borough = new BoroughClient("BOROUGH-...");
// Search rentals in the Upper East Side
const results = await borough.rentals.search({
areas: "120",
minBeds: 1,
maxPrice: 4000,
noFee: true,
});
console.log(`Found ${results.meta.total} listings`);
for (const listing of results.data) {
console.log(
`${listing.address.street}${listing.address.unit ? ` ${listing.address.unit}` : ""} — $${listing.price}/mo`,
);
}

The client accepts a string (API key) or a config object:

// Simple — just the API key
const borough = new BoroughClient("BOROUGH-...");
// Full config
const borough = new BoroughClient({
apiKey: "BOROUGH-...",
baseURL: "https://borough.qwady.app/v1", // default
timeout: 30_000, // 30s default
maxRetries: 2, // 2 retries on 429/5xx
});
// Environment variable fallback
// Set BOROUGH_API_KEY in your environment, then:
const borough = new BoroughClient();

The client exposes resource modules following a consistent pattern:

// Search
const rentals = await borough.rentals.search({ areas: "120", minBeds: 2 });
const sales = await borough.sales.search({ areas: "200", maxPrice: 1_000_000 });
// Property details
const listing = await borough.listings.get("4961849");
const history = await borough.listings.history("4961849");
const fees = await borough.listings.fees("4961849");
const openHouses = await borough.listings.openHouses("4961849");
// Building data
const building = await borough.buildings.get("12345");
const scores = await borough.buildings.scores("12345");
const violations = await borough.buildings.violations("12345");
const buildingListings = await borough.buildings.listings("12345");
// Areas
const allAreas = await borough.areas.list();
const neighborhoods = await borough.areas.list({ level: 2, parentId: 100 });
// Market data
const snapshot = await borough.market.snapshot({ areaId: 120, bedrooms: 1 });
const trends = await borough.market.trends({ areaId: 120 });
const comparison = await borough.market.compare({ areas: "120,200,300" });

The SDK throws typed errors that you can catch and handle:

import {
BoroughClient,
NotFoundError,
RateLimitError,
QuotaExceededError,
} from "@borough/sdk";
const borough = new BoroughClient("BOROUGH-...");
try {
const listing = await borough.listings.get("nonexistent");
} catch (error) {
if (error instanceof NotFoundError) {
console.log("Listing not found");
} else if (error instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
} else if (error instanceof QuotaExceededError) {
console.log("Monthly quota exceeded");
} else {
throw error;
}
}

All error classes extend APIError, which extends BoroughError:

Error ClassError CodeWhen
AuthenticationErrorMISSING_API_KEY, INVALID_API_KEY, EXPIRED_API_KEYBad or missing API key
RateLimitErrorRATE_LIMIT_EXCEEDEDToo many requests per minute
QuotaExceededErrorQUOTA_EXCEEDEDMonthly request quota exceeded
NotFoundErrorNOT_FOUNDResource doesn’t exist
ValidationErrorINVALID_PARAMSBad request parameters
TierRestrictedErrorTIER_RESTRICTEDEndpoint requires higher tier
UpstreamErrorUPSTREAM_ERRORData source temporarily unavailable
ConnectionErrorNetwork failure

The SDK automatically retries on 429 (rate limit) and 5xx (server error) responses with exponential backoff. Default: 2 retries, starting at 500ms. The Retry-After header is respected when present.

// Disable retries
const borough = new BoroughClient({ apiKey: "...", maxRetries: 0 });

The SDK is fully typed. All request parameters and response types are exported:

import type {
ListingSummary,
ListingDetail,
BuildingDetail,
BuildingScores,
SearchParams,
SaleSearchParams,
} from "@borough/sdk";