---
title: "Query companies"
method: POST
path: "/api/v1/ackdb/observe/companies/query"
tags: ["ackDB"]
---

# Query companies

`POST /api/v1/ackdb/observe/companies/query`

## Request

- Base URL: `http://localhost:3009`
- URL: `http://localhost:3009/api/v1/ackdb/observe/companies/query`
- Auth: HTTP bearer

Compiles the filter/sort/columns into parameterized read-only SQL (run on the ackdb_readonly role) and returns a page of company rows with pagination metadata. If the count query is rejected or times out, `total` is clamped and `pagination.totalIsLowerBound` is set to true.

## Request body

- AckdbObserveQueryRequest: Body for POST /observe/companies/query (and the /observe/contacts/query, /observe/persons/query twins). All fields optional - an empty body returns the default page of all active rows. The envelope is validated: an unknown top-level key, or a `group` node carrying an unknown key / a non-array `children`, is a 400 rather than a silently unfiltered query.
  - `filter` AckdbObserveFilterGroup: A boolean group combining child nodes with AND/OR.
    - `type` 'group', required
    - `op` 'and' | 'or', required: How children are combined.
    - `children` AckdbObserveFilter[], required
      - union: Recursive Observe filter DSL. A node is either a boolean `group` (with `op` and a `children` array of further nodes) or a leaf condition (`trait`, `attribute`, `event`, or `event_field`). The root node is always a group. Empty groups compile to TRUE (match all).
        - object: A boolean group combining child nodes with AND/OR.
          - `type` 'group', required
          - `op` 'and' | 'or', required: How children are combined.
          - `children` AckdbObserveFilter[], required
        - object: A condition on a registered entity trait. `traitKey` is whitelisted against the field catalog; the operator must be legal for the trait's valueType.
          - `type` 'trait', required
          - `traitKey` string, required
          - `operator` 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'is_null' | 'is_not_null' | 'in_last_days' | 'more_than_days_ago' | 'in_next_days', required
          - `value` union: Scalar or array depending on the operator. Omitted for is_null/is_not_null. For in_last_days, more_than_days_ago and in_next_days, a positive integer day count on a date field, at most 36500 days.
            - string
            - number
            - boolean
            - union[]
              - …
        - object: A condition on a fixed entity attribute (name, domain, status, timestamps, event counts, sources, last activity, or source presence).
          - `type` 'attribute', required
          - `field` 'name' | 'created_at' | 'updated_at' | 'domain' | 'status' | 'event_count' | 'last_active' | 'sources' | 'source_present', required
          - `operator` 'eq' | 'neq' | 'contains' | 'not_contains' | 'in' | 'gt' | 'gte' | 'lt' | 'lte' | 'within_days' | 'is_null' | 'is_not_null' | 'in_last_days' | 'more_than_days_ago' | 'in_next_days', required
          - `value` union: Scalar or array depending on the operator. Omitted for is_null/is_not_null. For in_last_days, more_than_days_ago and in_next_days, a positive integer day count on a date field, at most 36500 days.
            - string
            - number
            - union[]
              - …
        - object: A condition on the entity's event timeline. `eventType` is source-prefixed and whitelisted against the catalog. `withinDays` bounds the lookback; `count` is used by count_gte/count_lte.
          - `type` 'event', required
          - `eventType` string, required
          - `source` string
          - `predicate` 'exists' | 'not_exists' | 'count_gte' | 'count_lte', required
          - `count` integer
          - `withinDays` integer
        - object: A condition on a promoted event-metadata field's reduced scalar value (#212). `columnKey` is the synthetic field-catalog key (`emf__<source>__<field>` for the latest value, or `…__sum` for a numeric running total) and is whitelisted against the catalog; the operator must be legal for the field's valueType (same legality as a trait of that type).
          - `type` 'event_field', required
          - `columnKey` string, required
          - `operator` 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'in' | 'is_null' | 'is_not_null' | 'in_last_days' | 'more_than_days_ago' | 'in_next_days', required
          - `value` union: Scalar or array depending on the operator. Omitted for is_null/is_not_null. For in_last_days, more_than_days_ago and in_next_days, a positive integer day count on a date field, at most 36500 days.
            - string
            - number
            - boolean
            - union[]
              - …
  - `sort` AckdbSegmentSort[]
    - `field` string, required
    - `dir` 'asc' | 'desc', required
  - `columns` AckdbSegmentColumn[]
    - `field` string, required
    - `frozen` boolean
    - `width` number: Persisted column width in px (per-segment; presentation only).
  - `limit` integer: Page size, default 50. A limit above 1000 is a 400 - it is never clamped, because a clamped page size that the caller then steps `offset` by skips rows invisibly.
  - `offset` integer
  - `ids` string[]: Optional explicit id selection (pick specific rows instead of a filter). Each id is the row's grain id - entity_id for companies, contact_id for contacts, person_id for persons. When present, results are restricted to these ids, columns/sort still apply, any filter is ANDed in, and the id set becomes the page (limit/offset ignored). Empty array returns no rows; over 500 ids returns 400.

## Response `200`

A page of company rows.

- AckdbObserveQueryResponse: Response for the Observe company query. `data` rows always carry entityId, name, and domain plus the projected columns. `pagination.totalIsLowerBound` may be set when the count was clamped.
  - `data` object[], required
    - `entityId` string, required
    - `name` unknown
    - `domain` unknown
  - `pagination` AckdbPaginationMeta, required: Pagination metadata included in paginated responses.
    - `total` integer, required: Total number of records matching the query.
    - `limit` integer, required: Maximum number of records returned in this response.
    - `offset` integer, required: Number of records skipped.
    - `totalIsLowerBound` boolean: When true, `total` is a lower bound rather than an exact count. The Observe company query returns this when the count query was rejected or timed out and the result count was clamped (e.g. total=1000, totalIsLowerBound=true).

## Other responses

- `400`: Invalid filter - unknown field, illegal operator, or rejected SQL (BAD_REQUEST).

---

[API](https://skmtc.dev/floqer/apis/floqer-public-api.md) - [All operations](https://skmtc.dev/floqer/apis/floqer-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/floqer/apis/floqer-public-api/revisions/fce50dc07b20?raw)
