---
title: Query Products
slug: model-api/query-products
docTags: 
createdAt: 2026-04-14T12:16:39.866Z
---

The query endpoints let you search and filter [Product](docId:77yXhQWWag2_BXxTXuzIU) across models using a JSON-style query language. You can filter by [IFC](docId\:ST7bOMQTa9pkl5ACRtCu5), property values, quantities, and more. Each query returns a list of [Product](docId:77yXhQWWag2_BXxTXuzIU) objects.

## Filtering by model and revision

All query endpoints share the same filtering logic for models and revisions, controlled through the `model` and `revision` parameters. Both accept multiple values and are resolved in order:

1. If `model` is specified, the query targets the **latest revision** of each given model.
2. If only `revision` is specified, the query targets those exact revisions.
3. If neither is specified, the query targets the latest revision of every model in the project.

All endpoints accept `model` and `revision` as repeatable query parameters (e.g. `?model=id1&model=id2`). POST endpoints also accept `models` and `revisions` arrays (note the plural form) in the request body. When both are present, the body value takes precedence.

## List product fields

List the fields that can be queried for products.

```bash
GET /v2/projects/{project-id}/ifc/products/fields
```

**Query parameters**

| **Name**    | **Type** | **Description**                                                                                          |
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `data-mode` | String   | `explicit` (default) returns fields as stored. `inferred` merges type properties with product properties |
| `model`     | String   | Filter by model ID                                                                                       |
| `revision`  | String   | Filter by revision ID                                                                                    |
| `page`      | Number   | Page number (default `1`)                                                                                |
| `pageSize`  | Number   | Results per page (default `1000`)                                                                        |

**Example**

```curl
curl -X GET \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products/fields" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json'
```

**Response**

```json
[
  {
    "field": "attributes.GlobalId",
    "type": "string",
    "measureType": null
  },
  {
    "field": "attributes.Name",
    "type": "string",
    "measureType": null
  },
  {
    "field": "ifcType",
    "type": "string",
    "measureType": null
  },
  {
    "field": "objectId",
    "type": "number",
    "measureType": null
  },
  {
    "field": "quantitySets.BaseQuantities.quantities.Width",
    "type": "number",
    "measureType": "length"
  },
  {
    "field": "propertySets.Pset_WallCommon.properties.IsExternal",
    "type": "boolean",
    "measureType": null
  }
]
```

The `measureType` field indicates the physical measure type of a field. Common values include `"length"`, `"area"`, and `null` for non-measure fields.

Field names map directly to the [Product](docId:77yXhQWWag2_BXxTXuzIU) JSON structure. For example, `propertySets.Pset_WallCommon.properties.IsExternal` corresponds to the `IsExternal` property inside the `Pset_WallCommon` property set.

:::hint{type="info"}
A POST variant of this endpoint is also available. It accepts the same query parameters and an optional request body with `models` and `revisions` arrays.
:::

## Query suggested field values

Query suggested values for a specific field. This is useful for building autocomplete interfaces or discovering available values.

```bash
POST /v2/projects/{project-id}/ifc/products/fields/suggested_values
```

**Query parameters**

| **Name**    | **Type** | **Description**                                                                                          |
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `data-mode` | String   | `explicit` (default) returns fields as stored. `inferred` merges type properties with product properties |
| `model`     | String   | Filter by model ID                                                                                       |
| `revision`  | String   | Filter by revision ID                                                                                    |

**Request body**

| **Name**    | **Type** | **Description**                                                                              |
| ----------- | -------- | -------------------------------------------------------------------------------------------- |
| `field`     | String   | The field to get values for (required)                                                       |
| `search`    | String   | Search term to filter values, or an empty string to return the most common values (required) |
| `models`    | Array    | Filter by model IDs                                                                          |
| `revisions` | Array    | Filter by revision IDs                                                                       |

**Example**

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products/fields/suggested_values" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "field": "ifcType",
    "search": "wall"
  }'
```

**Response**

```json
[
  {
    "value": "IfcWall"
  },
  {
    "value": "IfcWallStandardCase"
  }
]
```

## Query products

Query products using a JSON-style query language based on the [MongoDB query language](https://docs.mongodb.com/manual/reference/operator/query/).

```bash
POST /v2/projects/{project-id}/ifc/products
```

**Query parameters**

| **Name**    | **Type** | **Description**                                                                                          |
| ----------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `data-mode` | String   | `explicit` (default) returns fields as stored. `inferred` merges type properties with product properties |
| `model`     | String   | Filter by model ID                                                                                       |
| `revision`  | String   | Filter by revision ID                                                                                    |
| `page`      | Number   | Page number (default `1`)                                                                                |
| `pageSize`  | Number   | Results per page (default `100`, max `1000`)                                                             |

**Request body**

| **Name**    | **Type** | **Description**                                                              |
| ----------- | -------- | ---------------------------------------------------------------------------- |
| `query`     | Object   | Query expression (required)                                                  |
| `fields`    | Object   | Field projection — use `1` to include specific fields or `0` to exclude them |
| `models`    | Array    | Filter by model IDs                                                          |
| `revisions` | Array    | Filter by revision IDs                                                       |

:::hint{type="info"}
Filter by model or revision when possible to reduce query scope and improve performance.
:::

**Example**

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "query": {
      "ifcType": { "$eq": "IfcWall" }
    },
    "fields": {
      "attributes.Name": 1,
      "ifcType": 1
    }
  }'
```

**Response**

Returns an array of [Product](docId:77yXhQWWag2_BXxTXuzIU) objects.

### Field projection

The `fields` parameter controls which fields are returned for each product. Use dot-notation to target nested fields. If `fields` is omitted or empty, all fields are returned.

**Include specific fields only:**

```json
{
  "fields": {
    "attributes.Name": 1,
    "ifcType": 1
  }
}
```

Returns only the listed fields.

:::hint{type="info"}
`objectId` is always included unless explicitly excluded with `"objectId": 0`.
:::

**Exclude specific fields:**

```json
{
  "fields": {
    "quantitySets": 0,
    "materials": 0
  }
}
```

Returns all fields except the listed ones.

:::hint{type="info"}
You can mix 1 and 0 in a single projection. Fields are processed in the order given, so overlapping paths may produce different results depending on their sequence.
:::

## Query language

### Implicit equality

A field mapped directly to a value implies `$eq`.

```json
{ "query": { "ifcType": "IfcWall" } }
```

is equivalent to:

```json
{ "query": { "ifcType": { "$eq": "IfcWall" } } }
```

### Comparison operators

### `$eq` — Equal

```json
{ "query": { "ifcType": { "$eq": "IfcWall" } } }
```

### `$ne` — Not equal

```json
{ "query": { "ifcType": { "$ne": "IfcSpace" } } }
```

### `$gt` / `$gte` — Greater than / Greater than or equal

```json
{ "query": { "quantitySets.BaseQuantities.quantities.Width": { "$gt": 0.2 } } }
```

```json
{ "query": { "quantitySets.BaseQuantities.quantities.Width": { "$gte": 0.19 } } }
```

### `$lt` / `$lte` — Less than / Less than or equal

```json
{ "query": { "quantitySets.BaseQuantities.quantities.Height": { "$lt": 3.0 } } }
```

```json
{ "query": { "quantitySets.BaseQuantities.quantities.Height": { "$lte": 2.8 } } }
```

### Set operators

### `$in` — Match any value in array

```json
{ "query": { "ifcType": { "$in": ["IfcWall", "IfcSlab", "IfcColumn"] } } }
```

### `$nin` — Match none of the values

```json
{ "query": { "ifcType": { "$nin": ["IfcSpace", "IfcOpeningElement"] } } }
```

### Logical operators

### `$and` — Match all conditions

```json
{
  "query": {
    "$and": [
      { "ifcType": "IfcWall" },
      { "attributes.Name": { "$regex": "exterior", "$options": "i" } }
    ]
  }
}
```

### `$or` — Match any condition

```json
{
  "query": {
    "$or": [{ "ifcType": "IfcWall" }, { "ifcType": "IfcSlab" }, { "ifcType": "IfcColumn" }]
  }
}
```

### `$not` — Negate a condition

```json
{
  "query": {
    "attributes.Name": {
      "$not": { "$regex": "internal", "$options": "i" }
    }
  }
}
```

### String operators

### `$regex` — Regular expression match

```json
{ "query": { "attributes.Name": { "$regex": "Wall" } } }
```

Use `$options` to set flags:

```json
{ "query": { "attributes.Name": { "$regex": "wall", "$options": "i" } } }
```

Common patterns:

| **Pattern**     | **Description**          |
| --------------- | ------------------------ |
| `"wall"`        | Contains "wall"          |
| `"^Basic Wall"` | Starts with "Basic Wall" |
| `"200mm$"`      | Ends with "200mm"        |

**Available&#x20;**`$options`**&#x20;flags**

| **Flag** | **Description**                            |
| -------- | ------------------------------------------ |
| `i`      | Case-insensitive matching                  |
| `m`      | Multi-line (`^`/`$` match line boundaries) |
| `s`      | Dot matches newlines                       |

:::hint{type="warning"}
Do not use regex syntax like `"/pattern/i"`. The pattern and options are always separate fields.
:::

### Existence operator

### `$exists` — Check if field is present

```json
{ "query": { "propertySets.Pset_WallCommon.properties.FireRating": { "$exists": true } } }
```

```json
{ "query": { "propertySets.Pset_WallCommon.properties.FireRating": { "$exists": false } } }
```

### Full-text search

### `$text` / `$search` — Search across all fields

Performs full-text search across all indexed string fields. Results are ranked by relevance.

```json
{ "query": { "$text": { "$search": "concrete structural" } } }
```

### IFC-specific operator

### `$ifcType` — Match IFC type hierarchy

Matches the specified IFC type **and all its subtypes**.

```json
{ "query": { "ifcType": { "$ifcType": "IfcWall" } } }
```

This matches `IfcWall`, `IfcWallStandardCase`, `IfcWallElementedCase`, etc.

In contrast, `$eq` matches only the exact type:

```json
{ "query": { "ifcType": { "$eq": "IfcWall" } } }
```

:::hint{type="info"}
$ifcType accepts a single string. To match multiple IFC types, wrap them in $or:

```json
{
  "query": {
    "$or": [{ "ifcType": { "$ifcType": "IfcWall" } }, { "ifcType": { "$ifcType": "IfcSlab" } }]
  }
}
```
:::

### Range queries

Combine comparison operators on the same field to create range queries:

```json
{
  "query": {
    "quantitySets.BaseQuantities.quantities.Width": {
      "$gte": 0.15,
      "$lte": 0.25
    }
  }
}
```

This is equivalent to using `$and`:

```json
{
  "query": {
    "$and": [
      { "quantitySets.BaseQuantities.quantities.Width": { "$gte": 0.15 } },
      { "quantitySets.BaseQuantities.quantities.Width": { "$lte": 0.25 } }
    ]
  }
}
```

## Examples

The examples below combine multiple operators to cover common real-world queries.

### Find exterior walls wider than 190mm

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products?data-mode=inferred" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "query": {
      "$and": [
        { "ifcType": { "$ifcType": "IfcWall" } },
        { "attributes.Name": { "$regex": "exterior", "$options": "i" } },
        { "quantitySets.BaseQuantities.quantities.Width": { "$gte": 0.19 } }
      ]
    }
  }'
```

### Find elements with fire rating

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "query": {
      "$and": [
        { "propertySets.Pset_WallCommon.properties.FireRating": { "$exists": true } },
        { "propertySets.Pset_WallCommon.properties.FireRating": { "$ne": "" } }
      ]
    }
  }'
```

### Full-text search

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "query": {
      "$text": { "$search": "concrete" }
    }
  }'
```

### Complex nested query

Find walls (including subtypes) that are not named "internal", and are either wider than 200mm or marked as external:

```curl
curl -X POST \
  "https://api.catenda.com/v2/projects/{project-id}/ifc/products?data-mode=inferred" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "query": {
      "$and": [
        {
          "$or": [
            { "ifcType": { "$ifcType": "IfcWall" } },
            { "ifcType": { "$ifcType": "IfcCurtainWall" } }
          ]
        },
        { "attributes.Name": { "$not": { "$regex": "internal", "$options": "i" } } },
        {
          "$or": [
            { "quantitySets.BaseQuantities.quantities.Width": { "$gte": 0.2 } },
            { "propertySets.Pset_WallCommon.properties.IsExternal": true }
          ]
        }
      ]
    }
  }'
```

## Operator quick reference

| **Operator** | **Type**   | **Example**                                         |
| ------------ | ---------- | --------------------------------------------------- |
| `$eq`        | Comparison | `{ "field": { "$eq": "value" } }`                   |
| `$ne`        | Comparison | `{ "field": { "$ne": "value" } }`                   |
| `$gt`        | Comparison | `{ "field": { "$gt": 10 } }`                        |
| `$gte`       | Comparison | `{ "field": { "$gte": 10 } }`                       |
| `$lt`        | Comparison | `{ "field": { "$lt": 10 } }`                        |
| `$lte`       | Comparison | `{ "field": { "$lte": 10 } }`                       |
| `$in`        | Set        | `{ "field": { "$in": ["a", "b"] } }`                |
| `$nin`       | Set        | `{ "field": { "$nin": ["a", "b"] } }`               |
| `$and`       | Logical    | `{ "$and": [ ... ] }`                               |
| `$or`        | Logical    | `{ "$or": [ ... ] }`                                |
| `$not`       | Logical    | `{ "field": { "$not": { ... } } }`                  |
| `$regex`     | String     | `{ "field": { "$regex": "pattern" } }`              |
| `$options`   | String     | `{ "field": { "$regex": "...", "$options": "i" } }` |
| `$exists`    | Existence  | `{ "field": { "$exists": true } }`                  |
| `$text`      | Search     | `{ "$text": { "$search": "terms" } }`               |
| `$ifcType`   | IFC        | `{ "ifcType": { "$ifcType": "IfcWall" } }`          |

