REHLAPI
rehl.us
API reference

REHL API

The REHL API lets your brokerage read its leads and listings programmatically, each lead with its live REHL AI temperature score. It also exposes public endpoints for listing search, neighborhood guides, home valuations, and pre-qualification, so you can build search and data experiences on top of REHL.

Introduction

The API is organized into two sections. The Developer API is authenticated with an API key and returns your brokerage's own data: your leads and your listings, and nothing from any other brokerage. The Public data section needs no key and serves the same active listing, guide, valuation, and review data that powers REHL consumer sites.

All requests are made over HTTPS. Responses are JSON unless noted (listing photos are served as image URLs). Timestamps are UTC in ISO 8601. Money is represented in integer cents.

Base URL

base url
https://api.rehl.us

This reference is served at docs.rehl.us; the API itself is served at api.rehl.us, the base URL above. Point your requests at api.rehl.us.

Authentication

Developer API requests are authenticated with a secret API key. Send it as a bearer token in the Authorization header. The X-API-Key header is also accepted. Keys look like rehl_sk_....

Request
# Bearer token (preferred)
curl "https://api.rehl.us/api/v1/leads" \
  -H "Authorization: Bearer rehl_sk_EXAMPLEkeyDONOTUSE0000000000000000000"

# Or the X-API-Key header
curl "https://api.rehl.us/api/v1/leads" \
  -H "X-API-Key: rehl_sk_EXAMPLEkeyDONOTUSE0000000000000000000"

Get your key

A brokerage admin creates an API key in the REHL app under Settings → API keys. API access is a feature of the Power plan (and any plan that includes API access). The full key is shown once, at creation, and only the key holder ever sees it; REHL stores only a hash, so a lost key cannot be recovered and must be replaced with a new one.

A key stops working immediately if it is revoked, or if the brokerage's plan loses API access. Keep keys server-side and never embed them in a browser or mobile app.

Rate limits

All requests are rate limited, both the authenticated Developer API and the public endpoints. When you exceed the limit the API responds with 429 Too Many Requests and a Retry-After header telling you how many seconds to wait. Back off and retry after that interval. Paginate with limit and offset rather than requesting large pages.

Errors

Errors use standard HTTP status codes and a small JSON body with an error code and a human-readable message.

StatusMeaning
401The API key is missing, invalid, revoked, or expired. The response is deliberately uniform and reveals no detail about which of these it was.
403The brokerage's plan does not include API access. Upgrade to a plan with API access to use the Developer API.
429Rate limited. Wait the number of seconds in the Retry-After header before retrying.
401 response
{
  "error": "invalid_api_key",
  "message": "Invalid or missing API key."
}

Developer API

Authenticated with your API key. Returns your brokerage's own data only.

Leads

Read your brokerage's own leads, newest first, with their live REHL AI temperature score and pipeline stage. Scoped to your brokerage by the API key, you only ever see your own leads.

GET/api/v1/leadsAPI key

List your brokerage's leads, newest first.

Query parameters

FieldTypeDescription
limitintegerPage size. 1 to 100. Defaults to a small page.
offsetintegerRows to skip, for pagination. Defaults to 0.
bandstringFilter by temperature band: cold, warm, hot, or very_hot. Any other value returns an empty page rather than an error.

Response fields

FieldTypeDescription
iduuidThe lead's stable identifier.
emailstring | nullLead email, when known.
phonestring | nullLead phone, when known.
temperature_scoreinteger | null0 to 100 REHL AI temperature, or null when unscored.
temperature_bandstring | nullcold, warm, hot, or very_hot, or null when unscored.
pipeline_stagestringCurrent pipeline stage.
acquisition_sourcestring | nullHow the lead entered, e.g. consumer_site, organic_search, referral.
created_attimestampWhen the lead was created (UTC, ISO 8601).
updated_attimestampWhen the lead was last updated (UTC, ISO 8601).

This endpoint returns identity basics, temperature, pipeline stage, and timestamps only. It does NOT return score internals (score_factors, model version), enrichment blobs, or any cross-surface identifier.

Request
curl "https://api.rehl.us/api/v1/leads?limit=25&band=hot" \
  -H "Authorization: Bearer rehl_sk_EXAMPLEkeyDONOTUSE0000000000000000000"
Response
{
  "results": [
    {
      "id": "00000000-0000-0000-0000-000000000001",
      "email": "buyer@example.com",
      "phone": "+15555550142",
      "temperature_score": 72,
      "temperature_band": "hot",
      "pipeline_stage": "engaged",
      "acquisition_source": "consumer_site",
      "created_at": "2026-07-01T14:05:00Z",
      "updated_at": "2026-07-12T09:22:00Z"
    }
  ],
  "page": { "limit": 25, "offset": 0, "total": 1 }
}

Listings

Read your brokerage's own listings, newest first: address, core facts, and status. Scoped to your brokerage by the API key.

GET/api/v1/listingsAPI key

List your brokerage's own listings, newest first.

Query parameters

FieldTypeDescription
limitintegerPage size. 1 to 100. Defaults to a small page.
offsetintegerRows to skip, for pagination. Defaults to 0.
statusstringFilter by status: active, pending, sold, expired, withdrawn, or unknown.

Response fields

FieldTypeDescription
iduuidThe listing's stable identifier.
listing_keystringThe MLS listing key.
address_line1string | nullStreet address.
city / state / postal_codestring | nullLocation fields.
list_price_centsinteger | nullList price in cents.
bedsinteger | nullBedroom count.
baths_full / baths_halfinteger | nullFull and half bath counts.
sqftinteger | nullLiving area, square feet.
year_builtinteger | nullYear the home was built.
property_typestring | nullProperty type.
statusstringListing status.
listed_attimestamp | nullWhen it went on market.

Address and core facts only. Enrichment blobs and internal columns are never returned on this endpoint.

Request
curl "https://api.rehl.us/api/v1/listings?limit=25&status=active" \
  -H "X-API-Key: rehl_sk_EXAMPLEkeyDONOTUSE0000000000000000000"
Response
{
  "results": [
    {
      "id": "00000000-0000-0000-0000-000000000002",
      "listing_key": "MFR-EXAMPLE-1001",
      "address_line1": "100 Flagler Ave",
      "address_line2": null,
      "city": "New Smyrna Beach",
      "state": "FL",
      "postal_code": "32169",
      "list_price_cents": 74900000,
      "beds": 3,
      "baths_full": 2,
      "baths_half": 1,
      "sqft": 2140,
      "year_built": 2019,
      "property_type": "single_family",
      "status": "active",
      "listed_at": "2026-06-20T13:00:00Z",
      "created_at": "2026-06-20T13:02:00Z",
      "updated_at": "2026-07-11T18:40:00Z"
    }
  ],
  "page": { "limit": 25, "offset": 0, "total": 1 }
}

Public data

No key required. The same active listing, guide, valuation, and review data that powers REHL consumer sites.

Listings

Anonymous, read-only access to active listing data. No key required. These power search and listing-detail experiences.

GET/listingsNo auth

Search active listings by location, price, and home facts.

Query parameters

FieldTypeDescription
locationstringFree-text city, ZIP, or street. A 5-digit value matches ZIP exactly; otherwise it matches city or street.
citystringExact city name.
postal_codestringExact 5-digit ZIP.
min_price_centsintegerMinimum list price, in cents.
max_price_centsintegerMaximum list price, in cents.
min_bedsintegerMinimum bedrooms (0 to 20).
min_bathsintegerMinimum full baths (0 to 20).
property_typestringFilter by property type.
avoid_sfhabooleanWhen true, exclude listings in FEMA Special Flood Hazard Areas.
near_latnumberLatitude to sort proximity by (paired with near_lng).
near_lngnumberLongitude to sort proximity by (paired with near_lat).
limitintegerPage size. Defaults to 24.
offsetintegerRows to skip, for pagination.

Each result carries the full listing shape; enrichment fields (walk_score, flood_zone, school and census data, climate risk) are populated when available and null otherwise. Photos come back as {url, primary} objects. On the search endpoint, enrichment_updated_at is null and price_history is empty, use the by-id endpoint for those.

Request
curl "https://api.rehl.us/listings?location=32169&min_beds=3&max_price_cents=90000000&limit=2"
Response
{
  "results": [
    {
      "id": "00000000-0000-0000-0000-000000000002",
      "listing_key": "MFR-EXAMPLE-1001",
      "address_line1": "100 Flagler Ave",
      "city": "New Smyrna Beach",
      "state": "FL",
      "postal_code": "32169",
      "latitude": 29.0258,
      "longitude": -80.9270,
      "list_price_cents": 74900000,
      "beds": 3,
      "baths_full": 2,
      "baths_half": 1,
      "sqft": 2140,
      "lot_sqft": 6500,
      "year_built": 2019,
      "property_type": "single_family",
      "status": "active",
      "description": "Coastal three-bedroom near Flagler Avenue...",
      "photos": [
        { "url": "https://media.example.com/mfr-example-1001/0.jpg", "primary": true }
      ],
      "listed_at": "2026-06-20T13:00:00Z",
      "school_district_data": null,
      "walk_score": 71,
      "transit_score": null,
      "bike_score": null,
      "flood_zone": "X",
      "climate_risk": null,
      "list_office_name": "Barsottelli Prime Real Estate",
      "list_agent_name": null,
      "updated_at_mls": "2026-07-11T18:40:00Z",
      "census_data": null,
      "amenities": null,
      "enrichment_updated_at": null,
      "price_history": []
    }
  ],
  "total": 1,
  "limit": 2,
  "offset": 0
}
GET/listings/suggestNo auth

Location typeahead over places that actually have active homes.

Query parameters

FieldTypeDescription
qrequiredstringTypeahead prefix, 2 to 80 characters. Digits match ZIPs; text matches cities and streets.
limitintegerMax suggestions. Defaults to 8.

Each suggestion has a kind (zip, city, or address), a display label, a value to feed back into the location search param, and count, the number of active listings backing it.

Request
curl "https://api.rehl.us/listings/suggest?q=new%20smyrna&limit=5"
Response
{
  "query": "new smyrna",
  "suggestions": [
    { "kind": "city", "label": "New Smyrna Beach, FL", "value": "New Smyrna Beach", "count": 42 },
    { "kind": "zip", "label": "32169", "value": "32169", "count": 18 }
  ]
}
GET/listings/by-slug/{subdomain}/{slug}No auth

Fetch a single listing by broker subdomain and URL slug.

Path parameters

FieldTypeDescription
subdomainrequiredstringThe broker subdomain the listing is displayed on.
slugrequiredstringThe listing's URL slug.

This endpoint returns a flatter shape than the by-id endpoint: baths is the full bathroom count only (half baths are not included), the MLS number is mls_number, and the brokerage's legal name plus the Article 19 attribution string are included for public display. Only published listings resolve here.

Request
curl "https://api.rehl.us/listings/by-slug/barsottelliprime/100-flagler-ave-new-smyrna-beach-fl-32169"
Response
{
  "id": "00000000-0000-0000-0000-000000000002",
  "mls_number": "MFR-EXAMPLE-1001",
  "address_line1": "100 Flagler Ave",
  "city": "New Smyrna Beach",
  "state": "FL",
  "postal_code": "32169",
  "list_price_cents": 74900000,
  "beds": 3,
  "baths": 2,
  "sqft": 2140,
  "year_built": 2019,
  "property_type": "single_family",
  "status": "active",
  "description": "Coastal three-bedroom near Flagler Avenue...",
  "photos": [
    { "url": "https://media.example.com/mfr-example-1001/0.jpg", "primary": true }
  ],
  "public_slug": "100-flagler-ave-new-smyrna-beach-fl-32169",
  "source": "mls",
  "brokerage": {
    "name": "Barsottelli Prime Real Estate",
    "subdomain": "barsottelliprime",
    "state": "FL"
  },
  "attribution": "Listing courtesy of Barsottelli Prime Real Estate. Information deemed reliable but not guaranteed. Property information is provided by Stellar MLS, the local IDX. Listings are the property of the listing office and are protected by copyright."
}
GET/listings/{listing_id}No auth

Fetch a single listing by its identifier, with full detail.

Path parameters

FieldTypeDescription
listing_idrequireduuidThe listing's stable identifier.
Request
curl "https://api.rehl.us/listings/00000000-0000-0000-0000-000000000002"
Response
{
  "id": "00000000-0000-0000-0000-000000000002",
  "listing_key": "MFR-EXAMPLE-1001",
  "address_line1": "100 Flagler Ave",
  "city": "New Smyrna Beach",
  "state": "FL",
  "postal_code": "32169",
  "latitude": 29.0258,
  "longitude": -80.9270,
  "list_price_cents": 74900000,
  "beds": 3,
  "baths_full": 2,
  "baths_half": 1,
  "sqft": 2140,
  "lot_sqft": 6500,
  "year_built": 2019,
  "property_type": "single_family",
  "status": "active",
  "description": "Coastal three-bedroom near Flagler Avenue...",
  "photos": [
    { "url": "https://media.example.com/mfr-example-1001/0.jpg", "primary": true },
    { "url": "https://media.example.com/mfr-example-1001/1.jpg", "primary": false }
  ],
  "listed_at": "2026-06-20T13:00:00Z",
  "walk_score": 71,
  "transit_score": null,
  "bike_score": null,
  "flood_zone": "X",
  "climate_risk": null,
  "list_office_name": "Barsottelli Prime Real Estate",
  "list_agent_name": null,
  "updated_at_mls": "2026-07-11T18:40:00Z",
  "census_data": null,
  "amenities": null,
  "enrichment_updated_at": "2026-07-10T02:00:00Z",
  "price_history": []
}

Neighborhood guides

Published neighborhood guides: editorial + local stats. No key required.

GET/neighborhood-guidesNo auth

List all published neighborhood guides.

Request
curl "https://api.rehl.us/neighborhood-guides"
Response
{
  "results": [
    {
      "slug": "new-smyrna-beach-fl",
      "name": "New Smyrna Beach",
      "city": "New Smyrna Beach",
      "search_city": "New Smyrna Beach",
      "state": "FL",
      "county": "Volusia",
      "content": { "tagline": "Coastal living on Florida's east coast", "highlights": ["Walkable Flagler Ave", "Surf breaks"] }
    }
  ],
  "total": 1
}
GET/neighborhood-guides/{slug}No auth

Fetch a single published guide by slug.

Path parameters

FieldTypeDescription
slugrequiredstringGuide slug, lowercase letters, digits, and hyphens.
Request
curl "https://api.rehl.us/neighborhood-guides/new-smyrna-beach-fl"
Response
{
  "slug": "new-smyrna-beach-fl",
  "name": "New Smyrna Beach",
  "city": "New Smyrna Beach",
  "search_city": "New Smyrna Beach",
  "state": "FL",
  "county": "Volusia",
  "content": {
    "tagline": "Coastal living on Florida's east coast",
    "meta_description": "A guide to living in New Smyrna Beach, FL.",
    "description": ["..."],
    "highlights": ["Walkable Flagler Ave", "Surf breaks"],
    "faqs": [{ "q": "Is it walkable?", "a": "The Flagler Ave district is." }]
  }
}

Valuations

TrueValue returns an honest, range-based home value estimate from local active-listing prices. When there is not enough local inventory to say anything responsible, the range fields are null.

GET/valuations/estimateNo auth

Estimate a home's value range (TrueValue).

Query parameters

FieldTypeDescription
ziprequiredstring5-digit ZIP of the home.
sqftintegerLiving area, square feet (100 to 100000).
bedsintegerBedroom count (0 to 20).
bathsintegerBath count (0 to 20).
property_typestringProperty type.
subdomainstringBroker subdomain whose licensed feed the comps come from. Without it the estimate returns insufficient_data (no range).

A broker subdomain is required to return a range: comps come from exactly one brokerage's licensed feed (an IDX rule). Without a resolvable subdomain the range fields are null and basis is insufficient_data.

basis is one of zip_active_listings, city_active_listings, or insufficient_data. confidence is one of high, moderate, or low.

Request
curl "https://api.rehl.us/valuations/estimate?zip=32169&sqft=2100&beds=3&subdomain=barsottelliprime"
Response
{
  "zip": "32169",
  "city": "New Smyrna Beach",
  "estimate_low_cents": 68000000,
  "estimate_mid_cents": 74500000,
  "estimate_high_cents": 81000000,
  "median_price_per_sqft_cents": 35500,
  "comp_count": 12,
  "sqft_used": 2100,
  "basis": "zip_active_listings",
  "confidence": "moderate"
}

Pre-qualification

A clearly-labeled affordability estimate for a buyer. This is a screen, not a lead: it persists nothing, creates no record, and never returns a denial. It is a browser-oriented form endpoint, not a pure server-to-server call, see the note below.

POST/pre-qualification/estimateNo auth

Estimate a buyer's affordability from self-entered numbers (browser form endpoint).

Body fields

FieldTypeDescription
context.annual_income_centsrequiredintegerAnnual gross income, in cents.
context.monthly_debt_centsintegerRecurring monthly debt payments, in cents. Defaults to 0.
context.down_payment_centsintegerDown payment, in cents. Defaults to 0.
context.target_citystringOptional target city.
context.target_statestringOptional 2-letter target state.
extra.turnstile_tokenstringBot-challenge token issued to the browser form widget. Required when bot protection is enabled; without a valid token the request is rejected with 400. This is why the endpoint is browser-oriented, not server-to-server.

This is a browser-oriented form endpoint, not a pure server-to-server call. It is rate limited and, when bot protection is enabled, it requires a challenge token obtained in the browser (extra.turnstile_token). A request without a valid token is rejected with 400, so a plain server-side call with no token will fail in production. It powers the affordability screen on REHL consumer sites.

Persists nothing financial. The response is always a labeled estimate; a $0 result means no room for a mortgage payment at those numbers, never a denial.

Request
curl -X POST "https://api.rehl.us/pre-qualification/estimate" \
  -H "Content-Type: application/json" \
  -d '{
    "context": {
      "annual_income_cents": 14000000,
      "monthly_debt_cents": 45000,
      "down_payment_cents": 8000000
    },
    "extra": {
      "turnstile_token": "<challenge token from the browser form widget>"
    }
  }'
Response
{
  "status": "applied",
  "max_price_cents": 62500000,
  "income_band": "100k_150k",
  "source": "rehl_estimate",
  "confidence": "high",
  "as_of": "2026-07-12",
  "disclosure_version": "prequal.estimate.v1.2026-07"
}

Reviews

Published reviews and the star average for a broker subdomain. Only published reviews cross this wire. An unknown subdomain returns an empty set, never a 404.

GET/reviews/by-subdomain/{subdomain}No auth

Published reviews and star average for a broker subdomain.

Path parameters

FieldTypeDescription
subdomainrequiredstringThe broker subdomain to read reviews for.
Request
curl "https://api.rehl.us/reviews/by-subdomain/barsottelliprime"
Response
{
  "average": 4.87,
  "count": 23,
  "reviews": [
    {
      "id": "00000000-0000-0000-0000-000000000003",
      "agent_name": "Ignacio Barsottelli",
      "reviewer_name": "A. Buyer",
      "rating": 5,
      "body": "Guided us through a coastal purchase with total clarity.",
      "published_at": "2026-06-30T16:00:00Z"
    }
  ]
}

Media

A listing's photos come back on the listing endpoints as {url, primary} objects. REHL also serves any listing photo through a stable cache URL using the pattern below. Use it directly as the src of an <img>, it is an image URL, not a JSON API call.

GET/media/{listing_id}/{media_ref}No auth

Image URL pattern for a listing photo.

Path parameters

FieldTypeDescription
listing_idrequireduuidThe listing the photo belongs to.
media_refrequiredstringThe photo's stable MLS media key, or a 0-based index into the listing's display-ordered photo list.

media_ref is either the photo's stable MLS media key or a 0-based positional index into the listing's photo list.

Request
<img
  src="https://api.rehl.us/media/00000000-0000-0000-0000-000000000002/0"
  alt="100 Flagler Ave" />
Returns the image bytes (an image/* response), served from the REHL cache. This is an image URL, not a JSON endpoint, point an <img> tag at it. A missing or unresolvable photo degrades to a neutral placeholder image rather than an error.