Docs
Pick the integration that fits: a script tag, an MCP server, or the API.
Three ways in
| You are | Use | Time |
|---|---|---|
| An agent or office with a website (WordPress, Squarespace, Wix, anything) | The embed script, or the hosted page under your own address | 5 minutes |
| Building with Claude Code, Cursor, or another AI tool | The MCP server. One line of config, then ask for a listing page. | 2 minutes |
| A developer with your own stack | The REST API, the JS SDK, or the OpenAPI spec | An afternoon |
Embed and hosted page
Create a publishable key in the dashboard and register your site URL. Then paste this where the search should appear:
<script src="https://homesidx.com/embed.js" data-key="pk_live_..."></script>Options: data-target="#search" renders into an element you choose; data-accent="#0f766e" sets the button color. The widget is an auto-sizing iframe with search, results, listing pages, photos, attribution, and the disclaimer already compliant. It only works on the URLs registered to your account.
No website? Every account has a hosted page at https://homesidx.com/s/your-name with your logo, color, and contact details, set in the dashboard. Custom domains are enabled per account.
MCP for AI tools
The MCP server exposes the feed as tools: search_listings, get_listing, get_listing_media, get_display_rules, get_feed_status. Use a secret key.
Claude Code:
claude mcp add --transport http homesidx https://homesidx.com/mcp --header "Authorization: Bearer mk_live_..."Cursor, Windsurf, and other clients that take a JSON config:
{
"mcpServers": {
"homesidx": {
"url": "https://homesidx.com/mcp",
"headers": { "Authorization": "Bearer mk_live_..." }
}
}
}Then: "Build a listing search page for Pasadena condos under $900k using the homesidx tools, following get_display_rules." The tool responses carry the attribution and disclaimer the page must show. The transport is Streamable HTTP, stateless, JSON responses.
SDK and OpenAPI
JavaScript and TypeScript, zero dependencies, runs in Node 18+, browsers, Workers, Deno, Bun. Import straight from this site or copy the file into your project:
import { HomesIdx } from "https://homesidx.com/sdk/homesidx.js"; // types: https://homesidx.com/sdk/homesidx.d.ts
const mls = new HomesIdx({ apiKey: process.env.HOMESIDX_API_KEY });
const { data, meta, attribution } = await mls.listings.search({ city: "Pasadena", beds_min: 3, price_max: 1500000 });
const one = await mls.listings.get(data[0].listing_key);
for await (const l of mls.listings.all({ postal_code: "91101" })) { /* every page */ }Other languages: generate a client from /openapi.json (OpenAPI 3.1). A ready-made Next.js site template with search, listing page, and compliant attribution is in the repo under templates/next-site; ask us for a copy.
Keys
| Key | Where | Rules |
|---|---|---|
mk_live_... secret | Servers, MCP, build scripts | Never ship to a browser. Full access. |
pk_live_... publishable | Browsers, the embed | Requests must come from a registered site URL (checked against the Origin). Cannot use the MCP endpoint. |
Send either as Authorization: Bearer <key> or X-Api-Key: <key>. Photo URLs under /v1/media need no key. CORS is enabled on all API routes.
REST reference
Base URL https://homesidx.com/v1.
| Method and path | Returns |
|---|---|
GET /v1/listings | Search results. Each listing carries its primary photo. |
GET /v1/listings/{key} | One listing with every displayable photo. |
GET /v1/listings/{key}/media | Photo list for a listing. |
GET /v1/media/{key}/{order} | Photo bytes. Cached on our CDN. Public. |
GET /v1/health | Feed status and last update time. |
POST /mcp | MCP endpoint (JSON-RPC 2.0). |
Search parameters
| Parameter | Meaning |
|---|---|
city | Exact city name, case-insensitive |
postal_code | 5-digit ZIP |
county | County name, e.g. Los Angeles |
status | Comma list of Active, ActiveUnderContract, Pending, ComingSoon (Closed when your key allows it) |
property_type | Comma list of Residential, ResidentialIncome, ResidentialLease, Land, CommercialSale, CommercialLease |
price_min, price_max | Whole dollars |
beds_min, baths_min, sqft_min, year_built_min | Integers |
pool | true or false |
bbox | west,south,east,north in decimal degrees |
lat, lng, radius_miles | Center point search; results ordered by distance. Radius defaults to 5, max 100 |
modified_since | ISO 8601 timestamp; returns listings changed after it. Use for polling |
agent_mls_id, office_mls_id | Scope to a listing agent or office |
sort | newest (default), price_asc, price_desc, updated |
limit | 1 to 100, default 25 |
cursor | Opaque value from meta.next_cursor |
Response envelope
Every JSON response has three keys. data is the payload. meta has counts, paging, and data_last_updated, which you must display. attribution has the MLS name, your brokerage as displaying broker, and the disclaimer text CRMLS requires on every page that shows listings.
{
"data": [ ... ],
"meta": { "count": 25, "total": 412, "limit": 25, "next_cursor": "bzoyNQ==", "data_last_updated": "2026-08-26T20:40:41Z" },
"attribution": {
"source": "California Regional Multiple Listing Service",
"displaying_broker": "Your Brokerage",
"mls_disclaimer": "Based on information from California Regional Multiple Listing Service, Inc. ..."
}
}Listing object
{
"listing_key": "1234567890",
"listing_id": "SR26009002",
"source": "CRMLS",
"status": "Active",
"property_type": "Residential",
"property_sub_type": "Condominium",
"price": { "list": 689000, "original": 709670, "close": null },
"address": { "full": "...", "street_number": "14100", "street_name": "Dickens Street", "unit": "212", "city": "Sherman Oaks", "state": "CA", "postal_code": "91423", "county": "Los Angeles" },
"location": { "lat": 34.1503, "lng": -118.4412 },
"details": { "beds": 2, "baths": 2, "living_area_sqft": 1180, "lot_size_sqft": null, "year_built": 1988, "stories": 1, "garage_spaces": 2, "pool": false, "hoa_fee": 395, "days_on_market": 7 },
"remarks": "Top-floor corner unit ...",
"dates": { "listed": "2026-07-15", "on_market": "2026-07-18", "closed": null, "status_changed": "...", "mls_updated": "..." },
"attribution": {
"listing_agent": { "name": "Daniel Kim", "mls_id": "...", "license": "02011223", "phone": "310-555-0177", "email": "..." },
"listing_office": { "name": "Compass", "mls_id": "...", "phone": "310-555-0150" },
"idx_contact_info": "Daniel Kim 310-555-0177",
"label": "Listing Agent: Daniel Kim | Listing Office: Compass | Daniel Kim 310-555-0177"
},
"photos_count": 24,
"media": [{ "order": 1, "url": "https://homesidx.com/v1/media/1234567890/1", "width": 1600, "height": 1067 }]
}Polling for changes
Store meta.data_last_updated and call /v1/listings?modified_since=...&sort=updated&limit=100 on a schedule. Listings that leave the feed stop appearing; treat a key you no longer receive as removed within 12 hours. Pro accounts can use webhooks instead.
Webhooks (Pro and Agency)
Instead of polling, register an https URL and receive listing.changed after every feed update (about every 15 minutes when anything changed). Manage them in the dashboard or with the API using a secret key:
POST /v1/webhooks { "url": "https://example.com/hooks/mls" } -> { "id": "...", "secret": "whsec_..." }
GET /v1/webhooks
POST /v1/webhooks/{id} send a ping now
DELETE /v1/webhooks/{id}Delivery body:
{ "event": "listing.changed", "listing_keys": ["1234567890", "..."], "since": "2026-08-26T20:15:00Z", "until": "2026-08-26T20:30:00Z", "count": 2 }Fetch each key with GET /v1/listings/{key}; a 404 means the listing left the feed. Headers: X-HomesIdx-Event, X-HomesIdx-Delivery, and X-HomesIdx-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + raw_body). Reject if the signature does not match or t is older than 5 minutes. Respond 2xx within 10 seconds; anything else is retried 5 times over 15 hours, then marked failed.
Display requirements
You are the advertising broker. CRMLS Rule 12.16 requires, on every listing you show:
attribution.label(or its parts) directly next to the price, beds and baths, square footage, or photo. Same font size and no lighter than the description.attribution.idx_contact_infoshown as delivered.- Any contact button must say who answers. "Contact Agent" alone is not allowed.
attribution.mls_disclaimerandmeta.data_last_updatedon the page.
The demo is a compliant reference layout; the embed and hosted page are built from it. Details on the compliance page.
Errors and limits
| Status | Body |
|---|---|
| 400 | { "error": "bad_request", "issues": [...] } |
| 401 | { "error": "unauthorized" } |
| 403 | { "error": "origin_not_allowed" } publishable key from an unregistered site, or { "error": "forbidden" } secret key required |
| 404 | { "error": "not_found" } |
| 429 | { "error": "rate_limited", "retry_after": 7 } or { "error": "quota_exceeded", "limit": 100000, "used": 100000 } |
Burst: 100 requests per 10 seconds per key. Monthly: Solo 100,000 calls, Pro 500,000, Agency 2,000,000. Photo requests are not metered.