# Search

> Search your listings and the Fondaro network with ranked text, filters, map areas, sorting and cursor pages.

`POST /properties/search` searches Fondaro listings: your own in any status, and the active listings of every agency on the Fondaro network. It combines ranked free text, structured filters, map areas, sorting and cursor pages. To search Resales Online, Zoddak, Inmobalia or property sites, use [Property sources](https://www.fondaro.com/docs/api/properties/sources.md).

Needs the `properties:read` scope. Send the key as `Authorization: Bearer <key>`; `X-API-Key` is still accepted. Every body field is optional: `{}` returns the newest active network listings.

## Search listings

### `POST /properties/search`

| Query parameter | Type | Meaning |
|---|---|---|
| `includeBranding` | boolean | Add `organizationBranding` and the `agent` card to each row |
| `includeAdCount`, `includeAdCreatives` | boolean | Add the ad creative count, or the creatives, to each row |
| `filterByOrganization` | boolean | Deprecated spelling of `"scope": "own"`; the response carries `Deprecation: true` |

```bash
curl -X POST https://api.fondaro.com/properties/search \
  -H "Authorization: Bearer fondaro_pk_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "villa \"sea views\" -golf",
    "city": ["Marbella", "Benahavís"],
    "price": { "min": 500000, "max": 2000000 },
    "bedrooms": { "min": 3 },
    "cursor": "*",
    "limit": 20
  }'
```

```json
{
  "results": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "referenceNumber": "FDR-7KQ2MX9A",
      "listingType": "sale",
      "propertyCategory": "residential",
      "propertyType": "house_detached",
      "city": "Marbella",
      "countryCode": "ES",
      "price": 1250000,
      "currency": "EUR",
      "bedrooms": 4,
      "bathrooms": 3,
      "livingArea": 320,
      "status": "active"
    }
  ],
  "total": 142,
  "nextCursor": "eyJ2IjoxLCJpdiI6Ii4uLiJ9"
}
```

## Choose whose listings

| `scope` | Returns |
|---|---|
| `network` | Every agency's active listings, yours included. The default. |
| `own` | Your agency's listings, in any status you ask for |

`partners` and `followed` are dashboard-only scopes; an API key gets `400` (`PROPERTY_FILTER_UNSUPPORTED`).

With no `scope`, asking for any non-active `status` makes the search `own`. These combinations are refused, so an own-listings request never turns into a network search:

| Request | Code |
|---|---|
| `network` with `?filterByOrganization=true` | `PROPERTY_SCOPE_CONFLICT` |
| `network` with a non-active `status` | `PROPERTY_SCOPE_NETWORK_ACTIVE_ONLY` |
| `network` with a `commission` range or sort | `PROPERTY_FILTER_UNSUPPORTED` |
| `agentId` or `agentUserIds` without `"scope": "own"` | `PROPERTY_FILTER_UNSUPPORTED` |

**Another agency's listing** comes back with everything except its private `commission`, `viewingInstructions` and import-only `additionalFeatures` keys. It keeps the street address, postcode, exact coordinates and `sharedCommission` (what the agency offers a collaborating agency). With `includeBranding=true`, its agent card has the agent's name, photo and title, plus email and phone numbers when that agent shares their contact with the network. Read `sharedCommissionNotes` with [`GET /properties/:id`](https://www.fondaro.com/docs/api/properties/crud.md#read-a-listing).

## Search by text

`query` takes web-search syntax and searches each listing's reference, title, town, community, region, postcode, type, description (every translation), amenities and street address. Accents and case are ignored, and English words match their other forms (`villas` finds `villa`).

| Syntax | Example | Matches |
|---|---|---|
| Words | `villa pool marbella` | Every word |
| `"phrase"` | `"La Quinta"` | The words together, in order |
| `-word` | `villa -golf` | Excludes the word |
| `OR` | `penthouse OR duplex` | Either word |
| Reference prefix | `FDR-A2B` | References starting with it |

Numbers are plain words: `3 bedrooms under 500000` is not read as filters. With a `query` and no sort, results are ranked by relevance (an exact reference first, then reference and title, then place and type, then description and amenities).

## Filter

| Field | Type | Values |
|---|---|---|
| `ids` | string[] | Listing UUIDs; other filters still apply |
| `referenceNumbers` | string[] | Exact, case-sensitive references |
| `agentId` | UUID | Your listings assigned to this roster agent (`"scope": "own"` only) |
| `agentUserIds` | string[] | Your listings assigned to any of these people, by user id (up to 50, `"scope": "own"` only) |
| `propertyType` | string[] | `apartment`, `condominium`, `house_detached`, `house_semi_detached`, `house_terraced`, `townhouse`, `multi_family_home`, `penthouse`, `studio`, `land_residential`, `land_commercial`, `land_agricultural`, `land_other`, `office`, `retail`, `commercial_other`, `industrial_warehouse`, `industrial_other`, `hospitality`, `other` |
| `propertyCategory` | string[] | `residential`, `commercial`, `industrial`, `land` |
| `listingType` | string[] | `sale`, `rent`, `sale_or_rent`, `fraction` |
| `status` | string[] | `active`, `inactive`, `pending`, `sold`, `rented`, `deleted` |
| `countryCode` | string[] | ISO 3166-1 alpha-2 codes |
| `region`, `city` | string[] | Names as [`stats/aggregations`](https://www.fondaro.com/docs/api/properties/stats.md) lists them |
| `bedrooms`, `bathrooms` | `{ min?, max? }` | Inclusive bounds |
| `price` | `{ min?, max? }` | In the listing's currency |
| `livingArea`, `plotArea` | `{ min?, max? }` | In the listing's area unit |
| `commission` | `{ min?, max? }` | Your private commission percentage; own listings only |
| `features` | string[] or object | Below |

An unknown enum value is a `400` naming the field. An `agentId` that is not on your roster is a `400` (`PROPERTY_FILTER_VALUE_INVALID`).

**Features.** A list of 1 to 30 slugs from the [amenity list](https://www.fondaro.com/docs/api/properties/crud.md#amenities-and-furnishing) returns listings with every one:

```json
{ "features": ["pool", "sea_view", "gated_complex"] }
```

The object form takes `hasPool`, `hasSeaView`, `hasGarage`, `hasTerrace`, `hasGarden`, `hasAirConditioning`, `hasLift` and `isFurnished`: `true` requires the feature, `false` excludes it. `isFurnished: true` matches partly and fully furnished listings.

## Search a map area

Send at most one shape. If several arrive, `boundingBox` wins, then `polygon`, then `location`.

| Field | Shape |
|---|---|
| `location` | `{ "lat": 36.51, "lon": -4.88, "distance": "5km" }` (radius in `km` or `m`, default `10km`) |
| `boundingBox` | `{ "northEast": { "lat", "lon" }, "southWest": { "lat", "lon" } }` |
| `polygon` | At least three `[longitude, latitude]` pairs; the ring is closed for you |

A `boundingBox` search returns up to 1,000 rows per page and no `total`.

## Sort

```json
{ "sort": [{ "field": "price", "order": "asc" }] }
```

Fields: `relevance`, `price`, `createdAt`, `updatedAt`, `publishedAt`, `bedrooms`, `bathrooms`, `livingArea`, `plotArea`, `commission`, `yearBuilt`, `city`, `countryCode`, `referenceNumber`, `status`, `propertyType`, `propertyCategory`, `listingType`. `order` is `asc` or `desc` (default `desc`). The older `sortBy` and `sortOrder` fields still work.

With no sort, a search with `query` is ordered by relevance and one without by most recently published. `relevance` cannot be combined with another field. Ties always break on the listing id, so the order is stable.

**Commission is private.** Another agency's `commission` never selects or orders its listings. With no `scope`, a `commission` range matches only your listings, and a `commission` sort puts other agencies' listings after yours. Compare offers with `sharedCommission` instead.

## Page through results

**Cursor (recommended).** Send `"cursor": "*"` for the first page, then the same body with each response's `nextCursor`. Listings published while you page never repeat or shift a page.

| Field | Type | Meaning |
|---|---|---|
| `cursor` | string | `*`, then the previous `nextCursor` |
| `limit` | integer | 1 to 100, default 20; may change between pages |

Only the first page carries `total`: exact up to 10,000, beyond that `10000` with `totalIsLowerBound: true`. Whether more exist is `nextCursor`, never a comparison with `total`. A cursor is bound to your organization, scope and criteria and lasts 30 minutes; anything else is a `400` (`PROPERTY_CURSOR_INVALID`).

**Page numbers.** Without `cursor`, send `page` (from 1) and `limit`; every page carries the exact `total`. `page` and `cursor` together are a `400`.

## Price histogram

Add `priceDistribution` to also get a histogram of every match, counted with all filters except `price`, so a price slider can show what lies either side.

| Field | Default | Meaning |
|---|---|---|
| `priceDistribution.interval` | `50000` | Bucket width |
| `priceDistribution.min`, `max` | none | Count only prices inside these bounds |

```json
{
  "priceDistribution": {
    "interval": 50000,
    "minPrice": 200000,
    "maxPrice": 5000000,
    "buckets": [{ "key": 200000, "count": 5 }, { "key": 250000, "count": 12 }]
  }
}
```

## Response fields

| Field | Meaning |
|---|---|
| `results` | Listing objects (see [Create and update listings](https://www.fondaro.com/docs/api/properties/crud.md#listing-fields)) |
| `total` | Exact count on page-number searches; first cursor page only otherwise |
| `totalIsLowerBound` | `true` when `total` is `10000` and more match |
| `nextCursor` | Present while more results exist (cursor searches) |
| `priceDistribution` | Only when requested |
| `relaxedFilters` | Present when a network search found nothing and was widened. The steps applied, in order: `features`, `query`, `listingType`, `price`, `priceDropped`, `bedrooms`, `propertyType` |

## What Ask Fondaro can do that the API cannot

Ask Fondaro searches properties with the same engine, but a few things differ from the routes with an API key. The assistant's tools are listed under [MCP server](https://www.fondaro.com/docs/api/mcp.md). The key routes are this page's `POST /properties/search` and, for other sources, `POST /property-sources/{source}/search` on [Property sources](https://www.fondaro.com/docs/api/properties/sources.md).

| Capability | Assistant | Key routes |
|---|---|---|
| Source, query, places, operation, property types, price, bedrooms, bathrooms, build size, plot size, features, stages, statuses, listing types, reference numbers, new developments, sort, map circle or polygon, cursor, up to 20 per page | Yes | Yes, with the same names and bounds |
| Room counts (`minRooms`, `maxRooms`) and a bounding-box map area | No | Yes |
| Whose listings: `scope` | Defaults to your own active listings (`own`) | Defaults to the whole network. Send `scope` explicitly |
| `scope` of partners or followed agencies, `organizationId`, `agentUserId`, and open houses within a number of days | Not offered | Refused with `400` and `PROPERTY_FILTER_UNSUPPORTED`; dashboard sessions only |
| Stable place rows (`locations`) | No | Dashboard sessions only; a key sends `locationIds` |
| One search across several sources at once | No | Not for keys; search each source in turn (`POST /property-sources/search` is for dashboard sessions) |
| Ranking by what the photos show (`visualPreference`) | Yes | No |
| Detail by an older listing reference | Resolved for you | Send the reference as the source returned it |

The daily allowance for property sites is shared with the assistant and the dashboard. The existing `POST /property-sources/{source}/search` answers a used-up allowance with `400` and `PROPERTY_PORTAL_QUOTA_EXHAUSTED`; the `/v1` routes answer `429` with `DAILY_LIMIT_REACHED`.

## Errors

| Status | Code | When |
|---|---|---|
| `400` | `PROPERTY_FILTER_UNSUPPORTED` | A filter or scope this caller or scope cannot use |
| `400` | `PROPERTY_FILTER_VALUE_INVALID` | An `agentId` not on your roster |
| `400` | `PROPERTY_SCOPE_CONFLICT`, `PROPERTY_SCOPE_NETWORK_ACTIVE_ONLY` | See [Choose whose listings](#choose-whose-listings) |
| `400` | `PROPERTY_CURSOR_INVALID` | Expired cursor, changed criteria, or `page` with `cursor` |
| `403` | `API_KEY_SCOPE_MISSING` | The key lacks `properties:read` |

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