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

# Query Documents

> Retrieve documents from a collection via MongoDB query (v2 endpoint).

AppDB supports the full range of MongoDB query predicates. The following operators are **not** supported:
- `$where` — JavaScript expression evaluation is disabled server-side.
- `$near` and `$nearSphere` — proximity searches require a geospatial index, which AppDB collections do not have.

All other operators in the [official MongoDB query predicate documentation](https://www.mongodb.com/docs/manual/reference/mql/query-predicates/) are supported, including comparison, logical, array, data type, bitwise, `$expr`, `$jsonSchema`, and `$regex` operators. The geospatial shape operators `$geoWithin` and `$geoIntersects` are also supported when your documents contain GeoJSON data.

**Querying by date**

AppDB has two categories of date fields that require different query approaches. Using the wrong approach for a field type fails silently — no error, just incorrect results.

**System date fields** (`createdOn`, `updatedOn`) are stored as BSON DateTime by AppDB. Query them using [MongoDB Extended JSON v2](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Date) `$date` syntax:
```json
{"createdOn": {"$gt": {"$date": "2026-01-01"}}}
```

**Content date fields** are stored as whatever JSON primitive you write. An ISO 8601 string like `"2026-09-01T18:30:00Z"` is stored as a BSON String. Two approaches work:

**String comparison** — ISO 8601 strings sort lexicographically in date order, so standard comparison operators work directly for both date-only (`YYYY-MM-DD`) and datetime (`YYYY-MM-DDTHH:MM:SSZ`) strings:
```json
{"content.startDate": {"$gt": "2026-01-01"}}
```

**`$expr` + `$toDate`** — converts the string to a date at query time, enabling `$date` comparisons and handling mixed-precision strings correctly:
```json
{
  "$expr": {
    "$gt": [
      {"$toDate": "$content.startDate"},
      {"$date": "2026-01-01"}
    ]
  }
}
```

<Warning>
  **Mixing up these approaches produces wrong results without an error.**

  Using `$date` against a content string field always returns empty — BSON DateTime never matches BSON String. Using plain string comparison against a system field like `createdOn` also always returns empty — BSON String never compares against BSON DateTime regardless of the operator used.

  To confirm the stored type of any field, use the `$type` operator. If `{"someField": {"$type": "date"}}` returns documents, use `$date` syntax. If `{"someField": {"$type": "string"}}` returns documents, use string comparison or `$expr` + `$toDate`.
</Warning>

**Aggregation examples**

The following examples use this sample movie collection:
```json
[
  {
    "content": {
      "title": "Inception", "genre": "sci-fi",
      "year": 2010, "rating": 8.8,
      "tags": ["mind-bending", "action"]
    }
  },
  {
    "content": {
      "title": "Interstellar", "genre": "sci-fi",
      "year": 2014, "rating": 8.6,
      "tags": ["mind-bending", "emotional"]
    }
  },
  {
    "content": {
      "title": "The Notebook", "genre": "romance",
      "year": 2004, "rating": 7.9,
      "tags": ["romantic", "drama"]
    }
  },
  {
    "content": {
      "title": "Titanic", "genre": "romance",
      "year": 1997, "rating": 7.8,
      "tags": ["romantic", "drama", "action"]
    }
  },
  {
    "content": {
      "title": "The Avengers", "genre": "action",
      "year": 2012, "rating": 8.0,
      "tags": ["action", "superhero"]
    }
  }
]
```

**Average rating per genre for movies released since 2010**
```
?groupby=content.genre&avg=content.rating avgRating&orderby=avgRating descending
```
Body:
```json
{"content.year": {"$gte": 2010}}
```
Response:
```json
[
  {"_id": "sci-fi", "avgRating": 8.7},
  {"_id": "action", "avgRating": 8.0}
]
```

**Movie count per genre**
```
?groupby=content.genre&count=count
```
Body: `{}`
```json
[
  {"_id": "sci-fi",  "count": 2},
  {"_id": "romance", "count": 2},
  {"_id": "action",  "count": 1}
]
```

**Tag frequency across all movies**

`unwind` produces one document per tag value before grouping, so a movie with `["action", "romantic"]` counts once toward each group.
```
?unwind=content.tags&groupby=content.tags&count=count&orderby=count descending
```
Body: `{}`
```json
[
  {"_id": "action",       "count": 3},
  {"_id": "romantic",     "count": 2},
  {"_id": "drama",        "count": 2},
  {"_id": "mind-bending", "count": 2},
  {"_id": "superhero",    "count": 1},
  {"_id": "emotional",    "count": 1}
]
```




## OpenAPI

````yaml /openapi/product/appdb.yaml post /api/datastores/v2/collections/{collectionId}/documents/query
openapi: 3.0.0
info:
  title: App DB API
  version: v1
  description: |
    The App DB API allows developers to interact with AppDB, a NoSQL database 
    for storing arbitrary JSON documents. This API supports CRUD operations, 
    querying, and aggregation, enabling developers to manage data efficiently 
    within their Domo applications.
servers:
  - url: https://{instance}.domo.com
    description: Domo Instance
    variables:
      instance:
        default: api
        description: Your specific Domo instance name (e.g., mycompany)
security:
  - developerToken: []
tags:
  - name: Documents
    description: Manage individual documents in an AppDB collection.
  - name: Collections
    description: Manage AppDB collections (schema, permissions).
  - name: App DB API (Product)
    description: Manage documents and collections in AppDB.
paths:
  /api/datastores/v2/collections/{collectionId}/documents/query:
    post:
      tags:
        - App DB API (Product)
      summary: Query Documents
      description: >
        Retrieve documents from a collection via MongoDB query (v2 endpoint).


        AppDB supports the full range of MongoDB query predicates. The following
        operators are **not** supported:

        - `$where` — JavaScript expression evaluation is disabled server-side.

        - `$near` and `$nearSphere` — proximity searches require a geospatial
        index, which AppDB collections do not have.


        All other operators in the [official MongoDB query predicate
        documentation](https://www.mongodb.com/docs/manual/reference/mql/query-predicates/)
        are supported, including comparison, logical, array, data type, bitwise,
        `$expr`, `$jsonSchema`, and `$regex` operators. The geospatial shape
        operators `$geoWithin` and `$geoIntersects` are also supported when your
        documents contain GeoJSON data.


        **Querying by date**


        AppDB has two categories of date fields that require different query
        approaches. Using the wrong approach for a field type fails silently —
        no error, just incorrect results.


        **System date fields** (`createdOn`, `updatedOn`) are stored as BSON
        DateTime by AppDB. Query them using [MongoDB Extended JSON
        v2](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-Date)
        `$date` syntax:

        ```json

        {"createdOn": {"$gt": {"$date": "2026-01-01"}}}

        ```


        **Content date fields** are stored as whatever JSON primitive you write.
        An ISO 8601 string like `"2026-09-01T18:30:00Z"` is stored as a BSON
        String. Two approaches work:


        **String comparison** — ISO 8601 strings sort lexicographically in date
        order, so standard comparison operators work directly for both date-only
        (`YYYY-MM-DD`) and datetime (`YYYY-MM-DDTHH:MM:SSZ`) strings:

        ```json

        {"content.startDate": {"$gt": "2026-01-01"}}

        ```


        **`$expr` + `$toDate`** — converts the string to a date at query time,
        enabling `$date` comparisons and handling mixed-precision strings
        correctly:

        ```json

        {
          "$expr": {
            "$gt": [
              {"$toDate": "$content.startDate"},
              {"$date": "2026-01-01"}
            ]
          }
        }

        ```


        <Warning>
          **Mixing up these approaches produces wrong results without an error.**

          Using `$date` against a content string field always returns empty — BSON DateTime never matches BSON String. Using plain string comparison against a system field like `createdOn` also always returns empty — BSON String never compares against BSON DateTime regardless of the operator used.

          To confirm the stored type of any field, use the `$type` operator. If `{"someField": {"$type": "date"}}` returns documents, use `$date` syntax. If `{"someField": {"$type": "string"}}` returns documents, use string comparison or `$expr` + `$toDate`.
        </Warning>


        **Aggregation examples**


        The following examples use this sample movie collection:

        ```json

        [
          {
            "content": {
              "title": "Inception", "genre": "sci-fi",
              "year": 2010, "rating": 8.8,
              "tags": ["mind-bending", "action"]
            }
          },
          {
            "content": {
              "title": "Interstellar", "genre": "sci-fi",
              "year": 2014, "rating": 8.6,
              "tags": ["mind-bending", "emotional"]
            }
          },
          {
            "content": {
              "title": "The Notebook", "genre": "romance",
              "year": 2004, "rating": 7.9,
              "tags": ["romantic", "drama"]
            }
          },
          {
            "content": {
              "title": "Titanic", "genre": "romance",
              "year": 1997, "rating": 7.8,
              "tags": ["romantic", "drama", "action"]
            }
          },
          {
            "content": {
              "title": "The Avengers", "genre": "action",
              "year": 2012, "rating": 8.0,
              "tags": ["action", "superhero"]
            }
          }
        ]

        ```


        **Average rating per genre for movies released since 2010**

        ```

        ?groupby=content.genre&avg=content.rating avgRating&orderby=avgRating
        descending

        ```

        Body:

        ```json

        {"content.year": {"$gte": 2010}}

        ```

        Response:

        ```json

        [
          {"_id": "sci-fi", "avgRating": 8.7},
          {"_id": "action", "avgRating": 8.0}
        ]

        ```


        **Movie count per genre**

        ```

        ?groupby=content.genre&count=count

        ```

        Body: `{}`

        ```json

        [
          {"_id": "sci-fi",  "count": 2},
          {"_id": "romance", "count": 2},
          {"_id": "action",  "count": 1}
        ]

        ```


        **Tag frequency across all movies**


        `unwind` produces one document per tag value before grouping, so a movie
        with `["action", "romantic"]` counts once toward each group.

        ```

        ?unwind=content.tags&groupby=content.tags&count=count&orderby=count
        descending

        ```

        Body: `{}`

        ```json

        [
          {"_id": "action",       "count": 3},
          {"_id": "romantic",     "count": 2},
          {"_id": "drama",        "count": 2},
          {"_id": "mind-bending", "count": 2},
          {"_id": "superhero",    "count": 1},
          {"_id": "emotional",    "count": 1}
        ]

        ```
      parameters:
        - name: collectionId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the collection.
        - name: groupby
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of properties to group by. Use dot notation for
            nested fields (e.g., `content.campaignName`). Top-level fields can
            be referenced directly.
        - name: count
          in: query
          schema:
            type: string
          description: Alias for a count aggregation.
        - name: avg
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of properties with aliases to average (e.g.,
            `content.clicks myAlias`). Use dot notation for nested fields.
        - name: min
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of properties with aliases to find the minimum.
            Use dot notation for nested fields.
        - name: max
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of properties with aliases to find the maximum.
            Use dot notation for nested fields.
        - name: sum
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of properties with aliases to sum. Use dot
            notation for nested fields.
        - name: unwind
          in: query
          schema:
            type: string
          description: >-
            Comma-separated list of array properties to unwind (deconstructs an
            array field, producing one document per element). Use dot notation
            for nested fields. Must be combined with at least one of `count`,
            `groupby`, `avg`, `min`, `max`, or `sum`.
        - name: orderby
          in: query
          schema:
            type: string
          description: >-
            Field or alias to order by, followed by `ascending` or `descending`
            (e.g., `totalClicks descending`). Accepts either an aggregation
            alias or a field name using dot notation.
        - name: limit
          in: query
          schema:
            type: integer
          description: Maximum number of results to return. Default is 10,000.
        - name: offset
          in: query
          schema:
            type: integer
          description: Number of results to skip before returning. Default is 0.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QueryRequest'
            example:
              content.region:
                $eq: West
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Document'
              example:
                - id: a8428d64-a01d-49b2-9144-ddeec001acf8
                  datastoreId: 39b88a97-8247-4c0d-ba1d-8ed78d1a6680
                  collectionId: 232e6d6c-46e9-49b6-a137-8933f0f05999
                  syncRequired: true
                  owner: '870010733'
                  createdBy: '870010733'
                  createdOn: '2026-06-23T20:58:06.567Z'
                  updatedOn: '2026-06-23T23:36:13.177Z'
                  updatedBy: '870010733'
                  content:
                    campaignName: Q2 Campaign
                    region: West
                    status: Active
                - id: fc28d0cb-92a5-40fd-8a7c-84547d63953a
                  datastoreId: 39b88a97-8247-4c0d-ba1d-8ed78d1a6680
                  collectionId: 232e6d6c-46e9-49b6-a137-8933f0f05999
                  syncRequired: true
                  owner: '870010733'
                  createdBy: '870010733'
                  createdOn: '2026-06-23T20:58:35.097Z'
                  updatedOn: '2026-06-23T21:03:40.861Z'
                  updatedBy: '870010733'
                  content:
                    campaignName: Red Campaign
                    region: West
                    status: Inactive
        '404':
          $ref: '#/components/responses/CollectionNotFound'
components:
  schemas:
    QueryRequest:
      type: object
      additionalProperties: true
      description: A MongoDB-style query object.
    Document:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the document.
        datastoreId:
          type: string
          description: ID of the datastore the document belongs to.
        collectionId:
          type: string
          description: ID of the collection the document belongs to.
        syncRequired:
          type: boolean
          description: Whether the document is pending a sync to the underlying dataset.
        owner:
          type: string
          description: >-
            User ID of the document owner. v2 endpoints return this as a string;
            v1 endpoints (List, Get, Query, Update) return it as a numeric
            integer. This differs from Collection and Datastore owner fields,
            which are always integers.
        createdBy:
          type: string
          description: >-
            User ID of the user who created the document. v2 endpoints always
            return this field. v1 endpoints return it only from Create Document;
            all other v1 endpoints (List, Get, Query, Update) omit it.
        createdOn:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the document was created.
        updatedOn:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the document was last updated.
        updatedBy:
          type: string
          description: >-
            User ID of the user who last updated the document. v2 endpoints
            return this as a string; v1 endpoints return it as a numeric
            integer.
        content:
          type: object
          additionalProperties: true
          description: Arbitrary JSON content of the document.
      example:
        id: a8428d64-a01d-49b2-9144-ddeec001acf8
        datastoreId: 39b88a97-8247-4c0d-ba1d-8ed78d1a6680
        collectionId: 232e6d6c-46e9-49b6-a137-8933f0f05999
        syncRequired: true
        owner: '870010733'
        createdBy: '870010733'
        createdOn: '2026-06-23T20:58:06.567Z'
        updatedOn: '2026-06-23T23:36:13.177Z'
        updatedBy: '870010733'
        content:
          campaignName: Q2 Campaign
          region: West
          status: Active
    ErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: A human-readable error message.
        status:
          type: integer
          description: HTTP status code.
        statusReason:
          type: string
          description: HTTP status reason phrase.
        toe:
          type: string
          description: Trace identifier for support; stands for Thread of Execution.
  responses:
    CollectionNotFound:
      description: Not Found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            message: >-
              Cannot find permission from source [USER:870010733] to target
              [MAGNUM_COLLECTION:a3f7d891-bc24-4e56-9a12-c7e3b0f845d2].
              Permission Service reports that one or both does not exist
            status: 404
            statusReason: Not Found
            toe: RG3808D5LS-DHKE4-TG5EB
  securitySchemes:
    developerToken:
      type: apiKey
      in: header
      name: X-DOMO-Developer-Token
      description: Domo Developer Token for authentication.

````