# Property sources

> Search the Fondaro network, your connected feeds and international property sites through one search, detail and location contract.

`/property-sources` gives one contract for every place a listing can come from: the Fondaro network, the feeds your organization has connected (Resales Online, Zoddak, Inmobalia) and international property sites. Discover what each source supports, then search, look up places and read a listing the same way everywhere.

Every route needs an API key with the `sources:read` scope (or a dashboard session with an active organization). Credentials belong to your organization: a source connected by another organization is never available to your key. Send the key as `Authorization: Bearer <key>`, as in every example here. `X-API-Key: <key>` is still accepted on these routes, and so is `Authorization: ApiKey <key>`.

A used-up daily allowance for property sites answers `400` with `PROPERTY_PORTAL_QUOTA_EXHAUSTED` on these routes; the newer `/v1` routes answer `429` with `DAILY_LIMIT_REACHED` instead. See [Errors and limits](https://www.fondaro.com/docs/api/errors-and-limits.md).

## Source ids

Use the canonical id in the URL.

| Family | Ids |
|---|---|
| Fondaro network | `internal` |
| Connected feeds | `resales_online`, `zoddak`, `inmobalia` |
| Property sites | `idealista`, `rightmove`, `zoopla`, `immobiliare`, `immoscout`, `immowelt`, `funda`, `seloger`, `otodom`, `realtor`, `homes`, `trulia`, `zillow`, `redfin`, `loopnet`, `propertyfinder`, `bayut`, `dubizzle`, `centris` |

An unknown id returns `400` with `INVALID_PROPERTY_SOURCE`.

## List sources

### `GET /property-sources`

Returns every source with its capabilities and your organization's connection state.

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

```json
[
  {
    "id": "zoddak",
    "label": "Zoddak",
    "family": "zoddak",
    "connected": false,
    "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"
    },
    "country": { "code": "ES", "label": "Spain" },
    "currency": "EUR",
    "capabilities": {
      "viewModes": ["list", "map"],
      "supportsDetail": true,
      "addByLink": false,
      "brochureEligible": true,
      "spend": "none",
      "facets": [
        { "facet": "price", "kind": "number_range", "range": "min_max", "verifiable": true }
      ]
    }
  }
]
```

| Field | Meaning |
|---|---|
| `connection.status` | `connected` (the latest live check of the saved credentials passed), `attention` (credentials saved but the check failed or could not run) or `disconnected` (nothing saved). The Fondaro network is always connected; property sites need no credentials of yours. |
| `connection.reason` | With `attention`: `credentials_incomplete`, `credentials_rejected`, `access_refused`, `provider_unavailable`, `network_unavailable` or `check_failed`. `connection.message` says what to fix. |
| `connected` | `true` only when `connection.status` is `connected`. |
| `country`, `countries` | The source's country, or the full list it can serve. |
| `capabilities.facets` | The filters the source accepts (below). |
| `capabilities.nativePageSize` | Rows in one of the source's own result pages, when it has one. |
| `capabilities.brochureEligible`, `brochureImageLimit` | Whether its listings can go in a brochure, and how many photos a brochure keeps from one listing. |
| `capabilities.spend` | `pooled_credit` for property sites (each real request uses your daily allowance), else `none`. |

A source that needs attention is refused like a disconnected one (`PROPERTY_SOURCE_NOT_CONNECTED`). Connection checks are cached for 6 hours after a pass and 5 minutes after a failure; changing the credentials checks again on the next read.

### Facets

Each facet names a filter the source accepts. Build your controls from them: a field that appears on returned listings does not mean you can filter by it.

| Field | Meaning |
|---|---|
| `facet`, `kind` | The filter, and `text`, `number_range`, `option`, `location` or `geometry` |
| `required` | The search must include it (property sites require a location or a map shape) |
| `multiple`, `discoverable` | Accepts several values; its values are listed by the options endpoint |
| `range`, `min`, `max`, `integer`, `rangeConstraint` | Numeric bounds the source accepts |
| `geometry` | Supported map shapes: `circle`, `polygon`, `boundingBox` |
| `verifiable` | Whether returned listings carry the facts to check this filter |

## Search one source

### `POST /property-sources/:source/search`

Searches one source with the filters its descriptor supports. A filter the source cannot apply, an unknown field or an invalid value is a `400`: criteria are never silently relaxed.

| Body field | Type | Meaning |
|---|---|---|
| `scope` | string | Fondaro network only: `network` (every agency's active listings, the default) or `own` (your listings, any status) |
| `query` | string | Free text, where supported (max 200) |
| `locationIds` | string[] | 1 to 20 tokens from the locations endpoint |
| `country` | string | Backend country, for sources that serve several |
| `operation` | string | Sale or rent, in the source's own values |
| `propertyTypes`, `features`, `stages`, `statuses` | string[] | Values from the options endpoint |
| `listingTypes` | string[] | `sale`, `rent`, `sale_or_rent`, `fraction` |
| `minPrice`, `maxPrice` | number | Price bounds in the source's currency |
| `minBedrooms`, `maxBedrooms`, `minBathrooms`, `maxBathrooms` | number | Bedroom and bathroom bounds |
| `minRooms`, `maxRooms` | number | Total rooms, for sources that count rooms instead of bedrooms |
| `minBuildSize`, `maxBuildSize`, `minPlotSize`, `maxPlotSize` | number | Area bounds in m² |
| `referenceNumbers` | string[] | Exact listing references, where supported (max 50) |
| `newDevelopments` | boolean | New builds only, where supported |
| `geo` | object | A supported `circle`, `polygon` or `boundingBox` |
| `sort` | string | A value from the `sort` options |
| `limit` | integer | 1 to 20 |
| `cursor` | string | The previous page's `nextCursor` |

```bash
curl -X POST https://api.fondaro.com/property-sources/internal/search \
  -H "Authorization: Bearer fondaro_pk_abc123" \
  -H "Content-Type: application/json" \
  -d '{"scope":"own","minPrice":250000,"minBedrooms":3,"limit":10}'
```

```json
{
  "source": "internal",
  "showing": 10,
  "total": 42,
  "results": [
    {
      "id": "8d2c9f0e-0b1a-4c4e-9f53-2f7a1d6c4b11",
      "source": "internal",
      "ref": { "source": "internal", "id": "8d2c9f0e-0b1a-4c4e-9f53-2f7a1d6c4b11" },
      "referenceNumber": "FND-1042",
      "title": "Villa with sea views",
      "price": 1250000,
      "currency": "EUR",
      "bedrooms": 4,
      "bathrooms": 3,
      "livingArea": 310,
      "city": "Marbella",
      "propertyType": "villa",
      "mainImage": "https://..."
    }
  ],
  "nextCursor": "eyJ2IjoxLCJrIjoi...",
  "receipt": {
    "source": "internal",
    "requested": { "scope": "own", "minPrice": 250000, "minBedrooms": 3 },
    "resolved": { "scope": "own", "minPrice": 250000, "minBedrooms": 3 },
    "appliedFacets": ["scope", "price", "bedrooms"],
    "criteriaLabel": "Your listings · from €250,000 · 3+ bedrooms",
    "verification": "result_verified",
    "excludedUpstreamMismatches": 0
  }
}
```

**Counts.** `total` alone is exact. `total` with `totalIsLowerBound: true` means at least that many (the Fondaro network stops counting at 10,000). No `total` means unknown, which is also the case on every continuation page: keep the first page's count while you page.

**Pages.** Send `{ "cursor": "<nextCursor>" }` to the same source for the next page. `nextCursor` decides whether more rows exist; never calculate pages from `total`. A cursor is bound to your organization, the source, the criteria and the page size. An expired or mismatched cursor returns `PROPERTY_CURSOR_INVALID`: start again with the original criteria.

**Receipt.** The receipt records the criteria requested and resolved and the filters applied. `verification: "result_verified"` means the returned facts support every applied filter; `wire_applied_only` means the filter was sent to the source but some returned facts could not confirm it.

**Rooms.** ImmoScout24, Immobiliare.it and Otodom filter by total rooms (`minRooms`, whole numbers) and offer no bedroom filter.

The body may repeat the route's source as `source` or `sources: ["<source>"]`; anything else returns `400`. Searching several sources at once is available in the dashboard only.

## Read one listing

### `GET /property-sources/:source/detail/:id`

Returns one listing, with its receipt, by the `ref.id` a search returned.

| Parameter | In | Meaning |
|---|---|---|
| `id` | path | `ref.id`, URL-encoded in full (provider ids can contain slashes or colons) |
| `country` | query | `ref.country`, when the row has one |

```bash
curl "https://api.fondaro.com/property-sources/idealista/detail/108123456?country=es" \
  -H "Authorization: Bearer fondaro_pk_abc123"
```

```json
{
  "id": "108123456",
  "source": "idealista",
  "ref": { "source": "idealista", "id": "108123456", "country": "es" },
  "title": "Apartment in Marbella",
  "price": 495000,
  "currency": "EUR",
  "bedrooms": 2,
  "description": "...",
  "images": ["https://..."],
  "receipt": { "source": "idealista", "verification": "result_verified" }
}
```

Keep the whole `ref` (`source`, `id`, `country`) in selections, links and caches: the same portal id in two countries is two listings. Fondaro network ids are UUIDs; `referenceNumber` is a separate display field.

On the Fondaro network, another agency's active listing includes its street address, postcode and exact coordinates; only that agency's private `commission` is withheld. Another agency's inactive listing returns `PROPERTY_SOURCE_LISTING_NOT_FOUND`.

## Look up places

### `GET /property-sources/:source/locations`

Type-ahead places in the source's own vocabulary.

| Parameter | Meaning |
|---|---|
| `q` | Text to match (required, max 200) |
| `country` | Country code, for sources that serve several. The Fondaro network, Resales Online and Zoddak reject it. |

```bash
curl "https://api.fondaro.com/property-sources/internal/locations?q=benah" \
  -H "Authorization: Bearer fondaro_pk_abc123"
```

```json
[
  { "locationId": "loc_1f9c...", "label": "Benahavís", "type": "city" },
  { "locationId": "loc_7a02...", "label": "La Zagaleta", "type": "community", "parent": "Benahavís" }
]
```

Send the complete `locationId` back in `locationIds`. Tokens belong to the organization and source that issued them and expire after 15 minutes; a property site's token also binds its country. On the Fondaro network, rows are `city`, `community` (an area inside a town, named in `parent`) or `region`, and several locations match a listing in any of them.

A location is optional on the Fondaro network, Resales Online and Zoddak (no location searches the whole feed). Inmobalia has no location filter. Property sites require a location or a supported `geo` shape.

## List filter values

### `GET /property-sources/:source/options/:facet`

Lists the values one filter accepts. `facet` is `property_type`, `feature`, `stage`, `operation`, `status`, `sort` or `scope`.

```bash
curl https://api.fondaro.com/property-sources/internal/options/property_type \
  -H "Authorization: Bearer fondaro_pk_abc123"
```

```json
[
  { "value": "villa", "label": "Villa" },
  { "value": "apartment", "label": "Apartment", "aliases": ["flat"] }
]
```

## Errors and limits

Errors carry a stable `code`, a safe `message` and, for search, `source` and `nextAction`. Handle the code, never the message text.

| Code | Meaning |
|---|---|
| `INVALID_PROPERTY_SOURCE` | Unknown source id |
| `PROPERTY_FILTER_VALUE_INVALID` | A field is invalid or not supported by this source; `issues` names it |
| `PROPERTY_SOURCE_NOT_CONNECTED` | Your organization has not connected this source, or its credentials need attention |
| `PROPERTY_CURSOR_INVALID` | The cursor expired or does not match the request |
| `PROPERTY_SOURCE_LISTING_NOT_FOUND` | The listing is not available |
| `PROPERTY_UPSTREAM_FAILED` | The source failed to answer |
| `PROPERTY_PORTAL_QUOTA_EXHAUSTED` | Today's property-site allowance is used up |

Property sites draw on one daily allowance per organization (300 requests a day, shared by the dashboard, Ask Fondaro, MCP and API keys). Each real request to a property site uses one unit, whichever route caused it; a cached answer uses none. The Fondaro network and your connected feeds use no allowance. HTTP rate limits apply to every request; see [API overview](https://www.fondaro.com/docs/api/overview.md).

Source: https://www.fondaro.com/docs/api/properties/sources
