Skip to main content

Installation

npm install @borough/sdk

Quick start

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.street} ${listing.unit} — $${listing.price}/mo`);
}

Configuration

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.com/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();

Resources

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("some-building-slug");
const scores = await borough.buildings.scores("some-building-slug");
const violations = await borough.buildings.violations("some-building-slug");
const buildingListings = await borough.buildings.listings("some-building-slug");

// Areas
const allAreas = await borough.areas.list();
const neighborhoods = await borough.areas.list({ level: 2, borough: 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" });

Error handling

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

Retries

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 });

TypeScript

The SDK is fully typed. All request parameters and response types are exported:
import type {
  ListingSummary,
  ListingDetail,
  BuildingDetail,
  BuildingScores,
  SearchParams,
  RentalSearchParams,
  SaleSearchParams,
} from "@borough/sdk";

Next steps