TypeScript SDK
A lightweight TypeScript client for Conduit's governed MLS data access. Copy-paste ready with full type safety.
[i]No npm package required
The client below is self-contained. Copy it into your project and customize as needed. No external dependencies beyond the standard Fetch API.
ConduitClient
typescript
interface QueryOptions {
method?: string
toolName?: string
arguments?: Record<string, unknown>
resourceUri?: string
}
interface GovernanceMetadata {
governed: boolean
antiTraining: boolean
stateless: boolean
rateLimitRemaining: number | null
rateLimitReset: number | null
billingStatus: string | null
}
interface ConduitResponse<T = unknown> {
data: T
governance: GovernanceMetadata
status: number
}
class ConduitClient {
private baseUrl: string
private apiKey: string
constructor(gatewayUrl: string, apiKey: string) {
this.baseUrl = gatewayUrl
this.apiKey = apiKey
}
async call<T = unknown>(options: QueryOptions): Promise<ConduitResponse<T>> {
let body: Record<string, unknown>
if (options.resourceUri) {
body = {
jsonrpc: '2.0',
method: 'resources/read',
params: { uri: options.resourceUri },
id: Date.now(),
}
} else {
body = {
jsonrpc: '2.0',
method: options.method ?? 'tools/call',
params: options.toolName
? { name: options.toolName, arguments: options.arguments ?? {} }
: undefined,
id: Date.now(),
}
}
const res = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (!res.ok) {
const error = await res.json().catch(() => ({ error: res.statusText }))
throw new ConduitError(res.status, error.error ?? 'Request failed', error)
}
const data = await res.json()
return {
data: data as T,
governance: {
governed: res.headers.get('X-Conduit-Governed') === 'true',
antiTraining: res.headers.get('X-Conduit-Anti-Training') === 'true',
stateless: res.headers.get('X-Conduit-Stateless') === 'true',
rateLimitRemaining: parseNum(res.headers.get('X-RateLimit-Remaining')),
rateLimitReset: parseNum(res.headers.get('X-RateLimit-Reset')),
billingStatus: res.headers.get('X-Conduit-Billing'),
},
status: res.status,
}
}
async listTools() {
return this.call({ method: 'tools/list' })
}
async searchProperties(args: Record<string, unknown>) {
return this.call({ toolName: 'search_properties', arguments: args })
}
async readListing(listingUri: string) {
return this.call({ resourceUri: listingUri })
}
}
class ConduitError extends Error {
status: number
details: unknown
constructor(status: number, message: string, details: unknown) {
super(message)
this.status = status
this.details = details
}
}
function parseNum(val: string | null): number | null {
if (!val) return null
const n = parseInt(val, 10)
return isNaN(n) ? null : n
}Usage examples
List available tools
typescript
const client = new ConduitClient(
'https://gateway.conduitapi.dev/s/austin-mls/reso-feed',
'cnd_live_xxxxxxxxxxxxxxxxxxxx'
)
const { data, governance } = await client.listTools()
console.log('Tools:', data)
console.log('Governed:', governance.governed)Search properties
typescript
const { data, governance } = await client.searchProperties({
city: 'Austin',
propertyType: 'Residential',
minPrice: 300000,
maxPrice: 500000,
limit: 25,
})
console.log('Results:', data)
console.log('Rate limit remaining:', governance.rateLimitRemaining)
// Check anti-training requirement
if (governance.antiTraining) {
console.log('Data must not be used for model training')
}Error handling
typescript
try {
const result = await client.searchProperties({ city: 'Austin' })
} catch (err) {
if (err instanceof ConduitError) {
switch (err.status) {
case 429:
const retryAfter = (err.details as { retry_after?: number }).retry_after
console.log(`Rate limited. Retry in ${retryAfter}s`)
break
case 403:
console.log('Access denied — check billing or MLS approval')
break
case 502:
console.log('MLS data feed unreachable — retry with backoff')
break
default:
console.error(`Error ${err.status}: ${err.message}`)
}
}
}Next: Python SDK →