Property Sources

Discover connected property sources and use one search, detail, location and option contract.

Use /property-sources to discover the sources available to your organization. The same endpoints accept a property API key or an authenticated dashboard JWT with an active organization. Source credentials belong to that organization; a source connected elsewhere does not become available to your key.

Endpoints

MethodPathResult
GET/property-sourcesSource descriptors and current organization connectivity
POST/property-sources/:source/searchUnified listings, receipt and optional continuation
GET/property-sources/:source/detail/:id?country=One unified listing with its receipt
GET/property-sources/:source/locations?q=&country=Location choices with opaque locationId values
GET/property-sources/:source/options/:facetSupported option values, labels and optional aliases
POST/property-sources/searchOne to five sources in one interleaved page (signed-in dashboard sessions only)
GET/property-sources/allowanceToday's use of the daily portal allowance (signed-in dashboard sessions only)

Discover capabilities

curl https://api.fondaro.com/property-sources \
  -H "Authorization: ApiKey fondaro_pk_abc123"

Each descriptor includes id, label, family, connected, connection, country information and capabilities. A disconnected source can include a display-safe reason.

connection is the verified state for your organization:

connection.statusMeaning
connectedThe saved credentials passed their latest live check (the Fondaro network is always connected; a property portal is connected when Fondaro's shared RealtyAPI key is configured, since portals have no per-organization credential).
attentionCredentials are saved but the latest check failed or could not run. connection.reason is one of credentials_incomplete, credentials_rejected, access_refused, provider_unavailable, network_unavailable or check_failed, and connection.message says what to fix.
disconnectedNothing is saved for this source.

connected is true only when connection.status is connected; it is kept for existing clients. A source that needs attention is refused like a disconnected one (PROPERTY_SOURCE_NOT_CONNECTED). Checks are cached per organization and credential for 6 hours after a pass and 5 minutes after a failure; changing the credentials checks them again on the next read.

{
  "id": "zoddak",
  "label": "Zoddak",
  "connected": false,
  "reason": "Zoddak did not accept this API token. Copy it again from your Zoddak portal, disconnect, and save the new one.",
  "connection": {
    "status": "attention",
    "reason": "credentials_rejected",
    "message": "Zoddak did not accept this API token. Copy it again from your Zoddak portal, disconnect, and save the new one.",
    "checkedAt": "2026-09-24T10:15:00.000Z"
  }
}

Capabilities describe supported views, detail, adding by link, brochure eligibility, image policy, spend and filter facets. Use these fields to build controls; a returned listing field does not imply a corresponding search filter. brochureImageLimit, when present, is the number of photos a brochure keeps from one listing. nativePageSize, when present (the property portals), is the number of rows in one of the source's own result pages. detailOptions lists the extra detail-request options a source honours. Resales Online declares viewerId (opening a listing through an agency's property-viewer share link). Fondaro uses it server-side for brochures. The HTTP detail endpoint accepts only country, and a viewer id is never part of a listing ref.

A facet can require a value, expose discoverable options, support multiple values, constrain a numeric range or name supported geometries. Respect its min, max, integer, range and rangeConstraint fields. verifiable describes whether returned facts can verify a filter; the search receipt records what was verified for that particular response.

Canonical source ids are internal (the Fondaro network), resales_online, zoddak, inmobalia, and the portal ids idealista, rightmove, immobiliare, immoscout, funda, zoopla, seloger, otodom, immowelt, realtor, homes, trulia, propertyfinder, dubizzle, centris, loopnet, zillow, redfin and bayut. Use the canonical id in the URL. Stored client-site aliases such as fondaro and resales-online are not endpoint source ids.

Search

Search one source with the fields its descriptor supports. Unknown fields, unsupported filters and invalid values fail explicitly; criteria are never silently relaxed.

curl -X POST https://api.fondaro.com/property-sources/internal/search \
  -H "Authorization: ApiKey fondaro_pk_abc123" \
  -H "Content-Type: application/json" \
  -d '{"scope":"own","minPrice":250000,"minBedrooms":3,"limit":10}'
InputMeaning
scopeown or network on sources that advertise scope; Fondaro defaults to active network listings. Signed-in dashboard sessions may also send partners or followed
organizationId, agentUserIdSigned-in dashboard sessions only: one agency's (or one of its agents') network listings, on sources that advertise scope
openHouseWithinDaysSigned-in dashboard sessions only: 1 to 14; only listings with a published open house for other agencies that has not ended and starts within that many days
queryText search, when supported
locationIdsOpaque tokens returned by this source's location endpoint
locationsSigned-in dashboard sessions only: stored location objects from location rows, instead of locationIds
countryBackend country selected from the descriptor
operation, propertyTypes, features, stages, statuses, sortSource-supported option values
minPrice, maxPricePrice bounds
minBedrooms, maxBedrooms, minBathrooms, maxBathroomsBedroom and bathroom bounds
minRooms, maxRoomsTotal-room bounds, distinct from bedrooms
minBuildSize, maxBuildSize, minPlotSize, maxPlotSizeArea bounds
referenceNumbersExact listing references, when supported
newDevelopmentsDevelopment filter, when supported
geoA supported circle, polygon or boundingBox
limitPage size from 1 to 20
cursorOpaque continuation from an earlier response

ImmoScout, Immobiliare and Otodom filter total rooms using minRooms, with integer minima. Otodom accepts up to ten. These sources do not advertise bedroom filters or room maxima. Returned room facts can still contain halves, ranges or an open upper bound (roomsTo: null). Genuine detail bedrooms remain separate.

The optional sources array accepts exactly one source matching the URL: this route searches the source its URL names, and more than one entry returns MULTI_SOURCE_NOT_SUPPORTED. To search one or more sources through one door, see One to five sources in one search. An optional body source must also match the URL.

Search responses include source, showing, results and receipt. The count has three states:

ResponseMeaning
total without totalIsLowerBoundExact number of matches
total with totalIsLowerBound: trueAt least that many; the source stopped counting
No totalUnknown: the provider did not supply a reliable count, or this is a continuation page

The Fondaro network counts on the first page only, up to 10,000: beyond that it returns total: 10000 with totalIsLowerBound: true, and later pages carry no count. Keep the first page's count while you page. Other sources never set totalIsLowerBound. Treat nextCursor as the continuation authority rather than calculating pages from total. To continue, send the cursor to the same source:

{ "cursor": "<nextCursor from the preceding response>" }

Cursors bind organization, source, criteria and page size. Reset pagination when any of those changes. Provider sessions remain inside the opaque cursor; do not extract or submit a separate Resales query id. An expired or mismatched cursor returns PROPERTY_CURSOR_INVALID; restart the search with its original criteria.

The receipt contains requested and resolved criteria, applied facets, redacted provider parameter names and excludedUpstreamMismatches. result_verified means returned facts support the applied filters. wire_applied_only means the request was mapped to the provider but some response facts could not verify it.

One to five sources in one search

POST /property-sources/search runs one set of criteria over 1 to 5 sources and returns one interleaved page. It is available to signed-in dashboard sessions only: a property API key receives 401. Keys, MCP and the assistant keep the one-source route for now.

curl -X POST https://api.fondaro.com/property-sources/search \
  -H "Authorization: Bearer <dashboard session token>" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": ["internal", "resales_online", "idealista"],
    "placeIds": ["<place id from GET /places>"],
    "propertyTypes": ["villa"],
    "minBedrooms": 3,
    "maxPrice": 1200000,
    "sort": "mixed",
    "unsupportedFacets": "ignore"
  }'
InputMeaning
sources1 to 5 distinct source ids, in display order. Always explicit; there is no "all sources"
placeIds1 to 5 place ids from GET /places; not combinable with geo
sortmixed (default), price_asc or price_desc
cursorThe previous page's nextCursor; anything sent beside it must repeat the original request
scopeown, network, partners or followed, sent only to sources that advertise scope (the Fondaro network); other sources are searched without it and never dropped for it
organizationIdOne agency's network listings, on the same sources as scope
openHouseWithinDays1 to 14, on the same sources as scope (see the one-source table)
unsupportedFacetsdrop_source (default) or ignore: what a source that cannot apply a requested filter does (below)
CriteriaThe one-source search fields (propertyTypes, minPrice, minBedrooms, features, geo, …) except agentUserId, locationIds, locations, sort and limit

Filters are an intersection, never widened silently. Each source compiles the request through its own contract. By default (drop_source), a source that does not offer a requested filter is not searched: its receipt says dropped: { "reason": "facet_unsupported", "facets": [...] }. With unsupportedFacets: "ignore", the source is searched without the filters it lacks, and without option values it cannot resolve, and its receipt names them in ignoredFacets (for example ["bathrooms"]), so the caller can say "not applied on this source". A place is never ignored: a source that cannot filter by place is still dropped. A source with a total-rooms filter and no bedroom filter receives the bedroom minimum as rooms. Filters a source applies but cannot check against its rows keep verification: "wire_applied_only" on that source's receipt.

Places. Each source receives its own location value for a place. When a source has no value for a place yet, Fondaro looks the place name up once at that source (on a portal this costs one unit of the daily allowance, shown as placeLookupUnits) and remembers an exact match. A place the source still cannot name, or whose country the source does not serve, drops that source with place_unresolved. Inmobalia has no location filter, so any placeIds drop it. A source your organization has not connected is dropped with not_connected.

Fondaro network rows default to the network scope: every agency's active listings, including your own. scope: "own" narrows them to your agency's active listings.

Order. mixed takes rows in turn from each source in the order of sources, each source in its own default order. price_asc and price_desc compare the price in EUR (USD converts at the latest stored rate; another currency, or no price, sorts last); ties keep source order. A source that offers the same sort receives it too. There is no newest or relevance sort across sources.

Pages. Each page asks every source that still has results for its next 12 rows and orders that batch, so order is per page, not across pages. nextCursor is absent when every source is exhausted. A cursor expires after 10 minutes and belongs to your organization and the original request; a changed request returns PROPERTY_CURSOR_INVALID.

Time limit. A source that has not answered within 8 seconds is reported in errors with PROPERTY_SOURCE_TIMEOUT and retryable: true, and the other sources' rows are returned. Loading more tries it again. A portal unit it already spent stays spent and appears on its receipt.

Duplicates are grouped, never removed. groups lists rows of this page that are the same home: imported_from when a Fondaro network listing was imported from another row on the page (Fondaro rows carry importedFrom: { source, id } for imported listings), and same_home when rows of different sources are within 100 metres, within 2% in EUR and have the same bedrooms. Show a group as one card with "Also on …".

{
  "sources": ["internal", "resales_online", "idealista"],
  "sort": "mixed",
  "results": [
    { "id": "…", "source": "internal", "ref": { "source": "internal", "id": "…" }, "importedFrom": { "source": "resales_online", "id": "R4381122" } },
    { "id": "R4381122", "source": "resales_online", "ref": { "source": "resales_online", "id": "R4381122" } },
    { "id": "i1", "source": "idealista", "ref": { "source": "idealista", "id": "i1", "country": "es" } }
  ],
  "receipts": {
    "internal": { "source": "internal", "verification": "result_verified", "upstreamUnits": 0, "placeLookupUnits": 0 },
    "resales_online": { "source": "resales_online", "verification": "result_verified", "upstreamUnits": 0, "placeLookupUnits": 0 },
    "idealista": { "source": "idealista", "verification": "result_verified", "upstreamUnits": 1, "placeLookupUnits": 1 }
  },
  "totals": { "internal": 41, "resales_online": 118 },
  "totalIsLowerBound": {},
  "errors": {},
  "groups": [
    {
      "id": "group:…",
      "refs": [
        { "source": "internal", "id": "…" },
        { "source": "resales_online", "id": "R4381122" }
      ],
      "reason": "imported_from"
    }
  ],
  "nextCursor": "<opaque>"
}

Receipts are shortened above; each also carries the usual requested, resolved, appliedFacets, wireFacets, criteriaLabel and excludedUpstreamMismatches. A dropped or failed source's receipt has its requested criteria and an empty resolution: read dropped and errors first. totals has per-source counts where the source counts, on the first page only; there is no total across sources.

Allowance

GET /property-sources/allowance returns today's use of the organization's daily portal allowance (the one the dashboard, MCP, the assistant and API keys draw). The dashboard shows what is left under Sites in Search once some is used:

{ "used": 86, "limit": 300, "resetsAt": "2026-09-25T00:00:00.000Z" }

A search over several portals costs up to one unit per portal per page (none on a cache hit), plus one unit for each first-time place lookup on a portal. upstreamUnits on a portal's receipt is 0 when its page came from the cache.

Locations, options and identity

Discover options using the advertised facet, such as /property-sources/internal/options/property_type or /property-sources/internal/options/scope. The response is an array of { "value": "...", "label": "..." } choices, with optional family and aliases.

Location responses contain locationId, label and optional type and parent. Pass the complete token to search; tokens belong to the organization and source that issued them and expire after 15 minutes. Portal tokens also bind the backend country. Fondaro, Resales and Zoddak location discovery reject a country qualifier rather than silently ignoring it.

Fondaro network location rows come in three kinds (type): city rows for the towns you can see, community rows (an area within a town, whose parent is the town) and region rows. Several locations in one search match a listing in any of them, so Benahavís with Nueva Andalucía returns both. A community matches only inside its own town, because one community name can exist in two towns.

Location is optional for the Fondaro network, Resales Online and Zoddak: a search without one covers the whole feed. Inmobalia has no location filter. Portals require a location or a supported geo shape.

In a signed-in dashboard session (not with an API key), each location row also carries location: { kind, value, label, parent?, country? }. It does not expire, so the dashboard stores it in saved views and tabs and sends it back as locations in place of locationIds. The search applies it to the same source and organization and verifies it like a token, including the portal backend country. API keys that send locations get PROPERTY_FILTER_VALUE_INVALID.

Every new search and detail row carries ref: { source, id, country? }. Preserve the entire reference in selections, links and caches. Identical portal ids in different countries refer to different listings. Fondaro ids are UUIDs; referenceNumber is a separate display and search field. Provider ids are opaque strings: URL-encode the whole id when fetching detail, including any slashes, colons or Unicode characters, and forward its country qualifier.

Fondaro network (internal) search defaults to the network scope: active listings from every agency on Fondaro. An active listing from another agency includes its street address, postcode and exact coordinates, in search and detail alike. Only the listing agency's private commission is withheld. Another agency's non-active listing is not returned, and its detail returns PROPERTY_SOURCE_LISTING_NOT_FOUND. Use scope: "own" to search your own listings.

Errors and metering

Typed errors include code, a safe message, and, for search errors, source and nextAction. Unsupported filters, invalid options, missing required locations, disconnected sources and expired cursors are explicit failures. PROPERTY_SOURCE_LISTING_NOT_FOUND identifies an unavailable detail; PROPERTY_UPSTREAM_FAILED identifies a provider failure; PROPERTY_SOURCE_TIMEOUT (several-source search only) identifies a source that did not answer in time. Handle the code rather than matching message text.

Each portal source meters itself: it consumes one unit of the organization's daily allowance per real call to the paid provider, whichever endpoint, tool or brochure caused it. Shared cache hits and repeated cached missing-detail results do not consume another unit. PROPERTY_PORTAL_QUOTA_EXHAUSTED reports exhaustion. HTTP request rate limits still apply to cached responses; see API Overview.