HomesIDX

Docs

Pick the integration that fits: a script tag, an MCP server, or the API.

Three ways in

You areUseTime
An agent or office with a website (WordPress, Squarespace, Wix, anything)The embed script, or the hosted page under your own address5 minutes
Building with Claude Code, Cursor, or another AI toolThe MCP server. One line of config, then ask for a listing page.2 minutes
A developer with your own stackThe REST API, the JS SDK, or the OpenAPI specAn 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

KeyWhereRules
mk_live_... secretServers, MCP, build scriptsNever ship to a browser. Full access.
pk_live_... publishableBrowsers, the embedRequests 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 pathReturns
GET /v1/listingsSearch results. Each listing carries its primary photo.
GET /v1/listings/{key}One listing with every displayable photo.
GET /v1/listings/{key}/mediaPhoto list for a listing.
GET /v1/media/{key}/{order}Photo bytes. Cached on our CDN. Public.
GET /v1/healthFeed status and last update time.
POST /mcpMCP endpoint (JSON-RPC 2.0).

Search parameters

ParameterMeaning
cityExact city name, case-insensitive
postal_code5-digit ZIP
countyCounty name, e.g. Los Angeles
statusComma list of Active, ActiveUnderContract, Pending, ComingSoon (Closed when your key allows it)
property_typeComma list of Residential, ResidentialIncome, ResidentialLease, Land, CommercialSale, CommercialLease
price_min, price_maxWhole dollars
beds_min, baths_min, sqft_min, year_built_minIntegers
pooltrue or false
bboxwest,south,east,north in decimal degrees
lat, lng, radius_milesCenter point search; results ordered by distance. Radius defaults to 5, max 100
modified_sinceISO 8601 timestamp; returns listings changed after it. Use for polling
agent_mls_id, office_mls_idScope to a listing agent or office
sortnewest (default), price_asc, price_desc, updated
limit1 to 100, default 25
cursorOpaque 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_info shown as delivered.
  • Any contact button must say who answers. "Contact Agent" alone is not allowed.
  • attribution.mls_disclaimer and meta.data_last_updated on 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

StatusBody
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.