> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gabin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced search

> Combine conditions on buildings, energy certificates, condominiums, owners, occupants and companies with AND and OR groups, count the results, then list them.

The advanced search is the multi-criteria search of the gabin.ai map, exposed as three endpoints:

| Endpoint | Weight | Use |
| - | - | - |
| `GET /v1/search/fields` | 1 | The catalog of fields you can filter on |
| `POST /v1/search/count` | 5 | Count the matches and break them down |
| `POST /v1/search` | 5 | List the matches, paginated by cursor |

Count and search weigh 5: each call counts five times against your per-minute rate and daily quota. See [limits](https://docs.gabin.ai/limits.md#request-weights).

A typical flow is: read the catalog once, build a request, count it while you refine the conditions, then list the results.

## Read the field catalog

`GET /v1/search/fields` returns every filterable field. Add `?target=buildings` or `?target=companies` to get only the fields of one target. The catalog does not touch the database, so you can cache it in your application.

```bash theme={null}
curl "https://api.gabin.ai/v1/search/fields?target=buildings" \
  -H "Authorization: Bearer $GABIN_API_KEY"
```

One field of the catalog:

```json theme={null}
{
  "id": "dpe.classe",
  "target": "buildings",
  "object": "dpe",
  "label": "Étiquette énergie du bâtiment",
  "description": "Classe A à G du DPE représentatif du bâtiment (logements). Passoire thermique : F ou G. 31 % des bâtiments diagnostiqués n'ont pas de classe.",
  "type": "enum",
  "operator": "in",
  "unit": null,
  "values": [
    { "code": "A", "label": "A" },
    { "code": "B", "label": "B" },
    …
    { "code": "G", "label": "G" }
  ],
  "source": "bdnb.batiment_groupe_dpe_representatif_logement.classe_bilan_dpe"
}
```

`label`, `description` and value labels are in French. `id` and value `code`s are stable: use them in your requests. `description` states what the data means and its limits (here, 31 % of diagnosed buildings have no class), which helps an AI agent pick the right field.

### What you can filter on

The catalog has 59 fields at the time of writing, grouped by `object`:

| Target | `object` | Examples of fields |
| - | - | - |
| `buildings` | `batiment` | `batiment.usage`, `batiment.annee_construction`, `batiment.nb_logements`, `batiment.hauteur`, `batiment.fibre`, `batiment.parking` |
| `buildings` | `copro` | `copro.presente`, `copro.type_syndic`, `copro.nb_lots`, `copro.periode_construction` |
| `buildings` | `proprietaire` | `proprietaire.identifie`, `proprietaire.forme_juridique`, `proprietaire.activite` |
| `buildings` | `occupant` | `occupant.activite`, `occupant.effectif`, `occupant.nb_entreprises` |
| `buildings` | `dpe` | `dpe.classe`, `dpe.classe_ges`, `dpe.energie_chauffage`, `dpe.isolation_murs`, `dpe.conso_ep_m2`, `dpe.annee` |
| `companies` | `entreprise` | `entreprise.activite`, `entreprise.effectif`, `entreprise.forme_juridique`, `entreprise.chiffre_affaires`, `entreprise.en_perte`, `entreprise.nom` |

Read the catalog for the full list: it is the source of truth.

## Build a request

A request has a target, a zone, conditions that all apply, and optional OR groups.

**target** `string` *required*

`buildings` returns BDNB buildings. `companies` returns registered offices.

**zone** `object` *required*

The area searched.

**properties**

**type** `string` *required*

`city`, `district`, `postal_code` or `department`. Departments are accepted for buildings only.

**code** `string` *required*

INSEE code (`"69266"`), IRIS code (`"692660101"`), postal code (`"69100"`) or department code (`"69"`).

**conditions** `array`

Conditions that all apply (AND). Empty: everything in the zone.

**any\_of** `array`

OR groups: `[{ "conditions": [ … ] }]`. At least one condition of each group applies. Up to 4 groups, 2 to 6 conditions per group.

**limit** `integer` (default: 20)

`POST /v1/search` only. Results per page, 1 to 100.

**cursor** `string`

`POST /v1/search` only. `meta.page.next_cursor` of the previous page.

A request holds at most 12 conditions in total, OR groups included.

### Conditions

Each condition names a field `id` and gives either `values` or `min`/`max`, according to the field's `operator`:

| `operator` | Field `type` | Condition |
| - | - | - |
| `in` | `enum` | `"values": ["F", "G"]`: any of the listed codes |
| `in_naf` | `naf` | `"values": ["68.20B", "41.20"]`: NAF sub-classes, classes or divisions |
| `between` | `number` | `"min"` and/or `"max"`, inclusive |
| `is` | `boolean` | `"values": ["true"]` or `"values": ["false"]` |
| `starts_with` | `text` | `"values": ["SCI"]`: a prefix |

A field that does not apply to the target is refused with `400 INVALID`:

```json theme={null}
{
  "error": {
    "code": "INVALID",
    "message": "entreprise.chiffre_affaires does not apply to target buildings.",
    "docs_url": "https://docs.gabin.ai/errors#invalid"
  }
}
```

### The zone is required

A search without a zone would scan tens of millions of rows. The API refuses it with [`ZONE_REQUIRED`](https://docs.gabin.ai/errors#zone_required):

```json theme={null}
{
  "error": {
    "code": "ZONE_REQUIRED",
    "message": "A zone is required: whole-France searches cannot be answered in time. Add zone { type, code }.",
    "docs_url": "https://docs.gabin.ai/errors#zone_required"
  }
}
```

A department is accepted for buildings, but not for companies:

```json theme={null}
{
  "error": {
    "code": "ZONE_REQUIRED",
    "message": "For companies, a department is too broad: use a city, a district or a postal code.",
    "docs_url": "https://docs.gabin.ai/errors#zone_required"
  }
}
```

To cover a whole department for companies, iterate over its cities with `GET /v1/zones/departments/{code}/cities` (see [zones and addresses](https://docs.gabin.ai/guides/zones-and-addresses.md#cities-of-a-department)), keeping your daily quota in mind.

## Example: pre-1975 apartment buildings rated F or G

This request looks for apartment buildings in Villeurbanne built in 1974 or earlier, with an energy class of F or G (commonly called *passoires thermiques*), that have either a professional condominium manager or an identified corporate owner. Those are the two counterparts a renovation company would contact.

```json theme={null}
{
  "target": "buildings",
  "zone": { "type": "city", "code": "69266" },
  "conditions": [
    { "field": "batiment.usage", "values": ["Résidentiel collectif"] },
    { "field": "batiment.annee_construction", "max": 1974 },
    { "field": "dpe.classe", "values": ["F", "G"] }
  ],
  "any_of": [
    {
      "conditions": [
        { "field": "copro.type_syndic", "values": ["professionnel"] },
        { "field": "proprietaire.identifie", "values": ["true"] }
      ]
    }
  ]
}
```

**1. Count the matches**

```bash curl theme={null}
curl -X POST "https://api.gabin.ai/v1/search/count" \
  -H "Authorization: Bearer $GABIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d @search.json
```

```javascript JavaScript theme={null}
const search = {
  target: "buildings",
  zone: { type: "city", code: "69266" },
  conditions: [
    { field: "batiment.usage", values: ["Résidentiel collectif"] },
    { field: "batiment.annee_construction", max: 1974 },
    { field: "dpe.classe", values: ["F", "G"] },
  ],
  any_of: [
    {
      conditions: [
        { field: "copro.type_syndic", values: ["professionnel"] },
        { field: "proprietaire.identifie", values: ["true"] },
      ],
    },
  ],
};

const res = await fetch("https://api.gabin.ai/v1/search/count", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GABIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(search),
});
const { data: count } = await res.json();
```

```python Python theme={null}
import os
import requests

search = {
    "target": "buildings",
    "zone": {"type": "city", "code": "69266"},
    "conditions": [
        {"field": "batiment.usage", "values": ["Résidentiel collectif"]},
        {"field": "batiment.annee_construction", "max": 1974},
        {"field": "dpe.classe", "values": ["F", "G"]},
    ],
    "any_of": [
        {
            "conditions": [
                {"field": "copro.type_syndic", "values": ["professionnel"]},
                {"field": "proprietaire.identifie", "values": ["true"]},
            ]
        }
    ],
}

res = requests.post(
    "https://api.gabin.ai/v1/search/count",
    json=search,
    headers={"Authorization": f"Bearer {os.environ['GABIN_API_KEY']}"},
)
count = res.json()["data"]
```

```json theme={null}
{
  "data": {
    "total": 150,
    "total_kind": "exact",
    "breakdowns": [
      {
        "field": "commune",
        "label": "Par commune",
        "rows": [{ "value": "Villeurbanne", "label": "Villeurbanne", "count": 150 }]
      },
      {
        "field": "dpe",
        "label": "Par étiquette DPE",
        "rows": [
          { "value": "F", "label": "F", "count": 106 },
          { "value": "G", "label": "G", "count": 44 }
        ]
      },
      {
        "field": "usage",
        "label": "Par usage",
        "rows": [{ "value": "Résidentiel collectif", "label": "Résidentiel collectif", "count": 150 }]
      }
    ]
  },
  "meta": {}
}
```

The count goes up to 100,000; beyond, `total_kind` is `lower_bound`. `breakdowns` is `null` when the breakdowns took too long.

**2. List the first page**

Send the same body to `POST /v1/search`, with a `limit`:

```bash theme={null}
curl -X POST "https://api.gabin.ai/v1/search" \
  -H "Authorization: Bearer $GABIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "buildings",
    "zone": { "type": "city", "code": "69266" },
    "conditions": [
      { "field": "batiment.usage", "values": ["Résidentiel collectif"] },
      { "field": "batiment.annee_construction", "max": 1974 },
      { "field": "dpe.classe", "values": ["F", "G"] }
    ],
    "any_of": [
      { "conditions": [
        { "field": "copro.type_syndic", "values": ["professionnel"] },
        { "field": "proprietaire.identifie", "values": ["true"] }
      ] }
    ],
    "limit": 3
  }'
```

```json theme={null}
{
  "data": [
    {
      "type": "building",
      "id": "bdnb-bg-1G7L-EQXD-R8W9",
      "address": "328 Rue Francis de Pressensé, 69100 Villeurbanne",
      "city": "Villeurbanne",
      "usage": "Résidentiel collectif",
      "construction_year": 1868,
      "housing_units": 5,
      "dpe_class": "F",
      "gabin_url": "https://gabin.ai/batiment/328-rue-francis-de-pressense-69100-villeurbanne-bdnb-bg-1G7L-EQXD-R8W9"
    },
    {
      "type": "building",
      "id": "bdnb-bg-1QJM-KNQL-XAV3",
      "address": "81 Rue des Charmettes, 69100 Villeurbanne",
      "city": "Villeurbanne",
      "usage": "Résidentiel collectif",
      "construction_year": 1900,
      "housing_units": 11,
      "dpe_class": "F",
      "gabin_url": "https://gabin.ai/batiment/81-rue-des-charmettes-69100-villeurbanne-bdnb-bg-1QJM-KNQL-XAV3"
    },
    {
      "type": "building",
      "id": "bdnb-bg-1ZWB-QM6U-CA6K",
      "address": "9 Rue du Maréchal Foch, 69100 Villeurbanne",
      "city": "Villeurbanne",
      "usage": "Résidentiel collectif",
      "construction_year": 1936,
      "housing_units": 5,
      "dpe_class": "G",
      "gabin_url": "https://gabin.ai/batiment/9-rue-du-marechal-foch-69100-villeurbanne-bdnb-bg-1ZWB-QM6U-CA6K"
    }
  ],
  "meta": {
    "page": {
      "next_cursor": "eyJ2IjoxLCJhIjoiYmRuYi1iZy0xWldCLVFNNlUtQ0E2SyIsIm8iOjMsImwiOjMsImYiOiJiOTM4OGIyOCIsInQiOm51bGx9.CZM9AbtR…",
      "limit": 3
    },
    "total": 4,
    "total_kind": "lower_bound"
  }
}
```

Results come in a stable order. While pages remain, `meta.total` is a lower bound (here, "at least 4"): the exact total is the one `POST /v1/search/count` returned.

**3. Follow the cursor**

Send the same body again with `cursor` set to `meta.page.next_cursor`. Keep the same target, zone and conditions: the cursor is bound to them. The page size stays the one of the first page.

```javascript JavaScript theme={null}
async function* searchAll(search) {
  let cursor;
  do {
    const res = await fetch("https://api.gabin.ai/v1/search", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.GABIN_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ ...search, limit: 100, cursor }),
    });
    if (!res.ok) throw new Error((await res.json()).error.message);
    const { data, meta } = await res.json();
    yield* data;
    cursor = meta.page.next_cursor ?? undefined;
  } while (cursor);
}
```

Each page weighs 5. With `limit: 100`, the 150 buildings of this example cost two pages, 10 units. A query reaches at most 10,000 results by following cursors. See [pagination](https://docs.gabin.ai/conventions.md#pagination).

Then open each building with `GET /v1/buildings/{id}`, and its `/condominium` and `/owners` sections, as shown in [buildings](https://docs.gabin.ai/guides/buildings.md#from-an-address-to-who-occupies-and-owns-the-building).

## Search companies

With `target: "companies"`, the search returns registered offices. This request lists real-estate holding companies (NAF `68.20B`) created in 2024 or later with their registered office in the 69002 postal code:

```bash theme={null}
curl -X POST "https://api.gabin.ai/v1/search" \
  -H "Authorization: Bearer $GABIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "companies",
    "zone": { "type": "postal_code", "code": "69002" },
    "conditions": [
      { "field": "entreprise.activite", "values": ["68.20B"] },
      { "field": "entreprise.annee_creation", "min": 2024 }
    ],
    "limit": 2
  }'
```

```json theme={null}
{
  "data": [
    {
      "type": "company",
      "siren": "100068667",
      "name": "ALBA PATRIMONIUM",
      "naf_code": "68.20B",
      "naf_label": "Location de terrains et d'autres biens immobiliers",
      "city": "LYON",
      "headcount_range": "NN",
      "headquarters_building_id": "bdnb-bg-A4GG-1T9C-AS86",
      "gabin_url": "https://gabin.ai/entreprise/alba-patrimonium-100068667"
    },
    {
      "type": "company",
      "siren": "100175124",
      "name": "SCI CURTIL SAINT CLAUDE",
      "naf_code": "68.20B",
      "naf_label": "Location de terrains et d'autres biens immobiliers",
      "city": "LYON",
      "headcount_range": "NN",
      "headquarters_building_id": "bdnb-bg-ZGAK-JDUV-6W2R",
      "gabin_url": "https://gabin.ai/entreprise/sci-curtil-saint-claude-100175124"
    }
  ],
  "meta": {
    "page": {
      "next_cursor": "eyJ2IjoxLCJhIjoiMTAwMTc1MTI0MDAwMTMiLCJvIjoyLCJsIjoyLCJmIjoiZDM0YWYxOGQiLCJ0IjpudWxsfQ.niy-CMcJ…",
      "limit": 2
    },
    "total": 3,
    "total_kind": "lower_bound"
  }
}
```

`headquarters_building_id` is the BDNB building of the registered office, when matched. Next: `GET /v1/companies/{siren}`.

## Result rows

Each row has a `type`:

* `building`: `id`, `address`, `city`, `usage`, `construction_year`, `housing_units`, `dpe_class`, `gabin_url`.
* `company`: `siren`, `name`, `naf_code`, `naf_label`, `city`, `headcount_range`, `headquarters_building_id`, `gabin_url`.

> [!NOTE]
>
> In search rows, `usage` is the BDNB label in French (`"Résidentiel collectif"`), the same codes as the `batiment.usage` field. `GET /v1/buildings` uses English values (`residential`, `tertiary`, `industrial`, `mixed`) instead.

## Advanced search or the simple endpoints?

| You need | Use |
| - | - |
| Buildings in a radius, a bounding box or as GeoJSON | `GET /v1/buildings` ([buildings](https://docs.gabin.ai/guides/buildings.md#find-buildings-in-an-area)) |
| Companies by name, with simple filters, or the largest companies of an activity | `GET /v1/companies` ([companies](https://docs.gabin.ai/guides/companies.md#search-companies)) |
| Conditions on condominiums, owners, occupants, insulation, heating, financial signals, or OR groups | `POST /v1/search` |
| The number of matches and their breakdown before listing | `POST /v1/search/count` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.