GaurGaur docs

Querying contracts

Send a structured query to a contract and get back governed rows.

The REST query API is the precise way to read a contract. You send a JSON request describing the dimensions, measures, filters, and sorting you want; Gaur returns governed rows. It's the right choice for dashboards and application features that need exact, repeatable results.

The endpoint

POST /v1/api/{consumer_slug}/query
Authorization: Bearer <api_key>
Content-Type: application/json

For the field-by-field schema, see the Query endpoint reference and the Query schema reference.

The request

A request has three parts:

{
  "contract_name": "product_performance",
  "query": {
    "dimensions": [
      { "name": "ordered_at", "time_grain": "month" },
      { "name": "category" }
    ],
    "measures": ["revenue_usd", "cogs_ratio"],
    "filters": [
      { "field": "category",   "operator": "eq",  "value": "jaffle" },
      { "field": "ordered_at", "operator": "gte", "value": "2026-01-01" }
    ],
    "sorts": [{ "field": "ordered_at", "direction": "asc" }],
    "limit": 100,
    "offset": 0
  },
  "context": { "store_id": "STR-008" }
}
FieldPurpose
contract_nameThe contract to query. Must be one the consumer exposes.
queryWhat to select: dimensions, measures, filters, sorts, paging.
contextValues used by the contract's row-level security. Optional.

Building the query

Everything in query refers to dimensions and measures declared by the contract. You can't select a column the contract doesn't expose; that's what makes the result governed.

dimensions

The attributes to group by. Each entry has a name; a time dimension can also take a time_grain:

"dimensions": [
  { "name": "ordered_at", "time_grain": "month" },
  { "name": "category" }
]

time_grain is one of hour, day, week, month, quarter, year, and has to be a grain the contract declared.

measures

A list of measure names to aggregate:

"measures": ["revenue_usd", "units_sold", "cogs_ratio"]

filters

Conditions applied to the result. Each filter has a field, an operator, and a value:

"filters": [
  { "field": "category",    "operator": "in",  "value": ["jaffle", "beverage"] },
  { "field": "revenue_usd", "operator": "gte", "value": 1000 }
]

A few important caveats (full list in the query schema reference):

  • Measure filters compile to HAVING. Putting a measure in field filters the aggregated result, not the raw rows.
  • in / not_in require non-empty arrays. An empty [] is rejected.
  • Multiple filters are ANDed. No OR; use in or split into two queries.

For date ranges, prefer inclusive start, exclusive end so adjacent months don't double-count boundary rows:

"filters": [
  { "field": "ordered_at", "operator": "gte", "value": "2026-04-01" },
  { "field": "ordered_at", "operator": "lt",  "value": "2026-05-01" }
]

sorts

Ordering for the result. Each entry has a field and a direction:

"sorts": [{ "field": "revenue_usd", "direction": "desc" }]

direction is asc or desc. Always specify a sort if you intend to page; without one the order can shift between calls and pagination becomes unreliable.

limit and offset

limit caps the number of rows returned; offset skips rows for paging. Gaur clamps limit to 10,000. Asking for more silently returns 10,000 with pagination.limit: 10000 in the response. See Pagination.

Row-level security context

If the contract uses row-level security, it needs values to decide which rows your request can see. Some come from the API key (auth context, fixed per key). Others you supply per request in the context field:

"context": { "store_id": "STR-008" }

You send request context when one API key serves many end users, like a customer-facing dashboard. Your application authenticates the user, your backend derives the value that scopes them, and you put it in context.

Set request context on your trusted backend, never from the end user. If a user can choose their own store_id, they can read other stores' data. Derive the value from the authenticated session server-side, and don't let the client set it or see it.

A caller can't widen its own scope. context only ever narrows results to what the contract's RLS allows.

The response

A successful query returns 200 with a paginated result. Rows live at data.pagination.items (not at the top of data):

{
  "data": {
    "pagination": {
      "items": [
        { "ordered_at": "2026-04-01T00:00:00", "category": "jaffle",   "revenue_usd": 84210.50, "cogs_ratio": 0.42 },
        { "ordered_at": "2026-04-01T00:00:00", "category": "beverage", "revenue_usd": 39780.25, "cogs_ratio": 0.18 }
      ],
      "total_items":     24,
      "page":            1,
      "limit":           100,
      "total_pages":     1,
      "has_next_page":   false,
      "has_prev_page":   false
    },
    "execution_time_ms": 42
  },
  "message": null
}
  • data.pagination.items: the rows, one object per group, keyed by your selected dimensions and measures.
  • data.pagination: paging metadata. See Pagination for the full envelope and the 10k clamp.
  • data.execution_time_ms: query execution time in milliseconds.

A minimal query

You don't have to use every field. The smallest useful query is a contract name and a measure:

{
  "contract_name": "product_performance",
  "query": { "measures": ["revenue_usd"] }
}

That returns one row: total revenue_usd across every product, store, and date the contract reaches.

Next

On this page