Documentation Index
Fetch the complete documentation index at: https://qwady.wiki/llms.txt
Use this file to discover all available pages before exploring further.
Installation
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.address.street}${listing.address.unit ? ` ${listing.address.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.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();
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("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" });
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 Class | Error Code | When |
|---|
AuthenticationError | MISSING_API_KEY, INVALID_API_KEY, EXPIRED_API_KEY | Bad or missing API key |
RateLimitError | RATE_LIMIT_EXCEEDED | Too many requests per minute |
QuotaExceededError | QUOTA_EXCEEDED | Monthly request quota exceeded |
NotFoundError | NOT_FOUND | Resource doesn’t exist |
ValidationError | INVALID_PARAMS | Bad request parameters |
TierRestrictedError | TIER_RESTRICTED | Endpoint requires higher tier |
UpstreamError | UPSTREAM_ERROR | Data source temporarily unavailable |
ConnectionError | — | Network 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,
SaleSearchParams,
} from "@borough/sdk";
Next steps