Skip to main content

Queries

Queries are the read operations of your API. Each query is defined by a Dataverse QueryExpression (entity, columns, filters, orders, and related-entity links) plus its input parameters and access control. The engine exposes all three query kinds at the same route shape — GET /{module}/{query-name} — and switches behavior based on the query's multiplicity.

Query Types

Query TypeMultiplicityDescriptionUse Case
SingleSingleReturns exactly one record (or 204 No Content)Get record by ID, get current user's profile
MultiAllReturns all matching records as an array (no pagination)Get all active statuses, get team members
PaginatedSlicedReturns paged results with a paging cursorList all customers, search products

Single Query

A Single Query returns exactly one record based on the query criteria.

Configuration

Query: GetAccountById
Type: Single
Module: Sales

Configuration:
├── Entity: Account
├── Parameters:
│ └── accountId (GUID, required)
├── Filter: accountid = @accountId
├── Columns:
│ ├── accountid
│ ├── name
│ ├── telephone1
│ ├── emailaddress1
│ ├── primarycontactid (expand: fullname, emailaddress1)
│ └── ownerid (expand: fullname)
└── Authorization: All authenticated users

API Request

GET /sales/get-account-by-id?accountId={accountId}

API Response

Success (200 OK):

{
"accountId": "a1b2c3d4-...",
"name": "Contoso Ltd",
"telephone1": "+1-555-0100",
"emailAddress1": "info@contoso.com",
"primaryContact": {
"id": "contact-guid",
"name": "Jane Doe"
},
"owner": {
"id": "user-guid",
"name": "John Smith"
}
}

No Content (204): If no record matches the query criteria, the API returns 204 No Content with an empty response body.


Multi Query

A Multi Query returns multiple records without pagination. Best for bounded result sets.

Configuration

Query: GetAccountContacts
Type: Multi
Module: Sales

Configuration:
├── Entity: Contact
├── Parameters:
│ └── accountId (GUID, required)
├── Filter: parentcustomerid = @accountId AND statecode = 0
├── Order By: fullname ASC
├── Columns:
│ ├── contactid
│ ├── fullname
│ ├── emailaddress1
│ ├── telephone1
│ └── jobtitle
├── Max Results: 100
└── Authorization: All authenticated users

API Request

GET /sales/account-contacts?accountId={accountId}

API Response

Success (200 OK):

[
{
"contactId": "c1-guid",
"fullName": "Alice Johnson",
"emailAddress1": "alice@contoso.com",
"telephone1": "+1-555-0101",
"jobTitle": "CEO"
},
{
"contactId": "c2-guid",
"fullName": "Bob Williams",
"emailAddress1": "bob@contoso.com",
"telephone1": "+1-555-0102",
"jobTitle": "CFO"
}
]

Empty result (200 OK): If no records match, a Multi query returns 200 OK with an empty array []. (Only Single queries return 204 No Content when nothing matches.)

When to Use Multi Query

ScenarioRecommendation
Results always bounded (e.g., max 50 team members)✅ Use Multi Query
Results could grow unbounded❌ Use Paginated Query
Need to load all records at once for UI✅ Use Multi Query
Need to display with infinite scroll❌ Use Paginated Query

Paginated Query

A Paginated Query returns results in pages, ideal for large datasets that need efficient loading and navigation.

Configuration

Query: SearchAccounts
Type: Paginated
Module: Sales

Configuration:
├── Entity: Account
├── Parameters:
│ ├── searchTerm (String, optional)
│ ├── categoryCode (Integer, optional)
│ └── ownerIdFilter (GUID, optional)
├── Filter:
│ │ (name LIKE '%@searchTerm%' OR accountnumber LIKE '%@searchTerm%')
│ │ AND (@categoryCode IS NULL OR accountcategorycode = @categoryCode)
│ │ AND (@ownerIdFilter IS NULL OR ownerid = @ownerIdFilter)
│ │ AND statecode = 0
├── Order By: name ASC
├── Columns:
│ ├── accountid
│ ├── name
│ ├── accountnumber
│ ├── telephone1
│ ├── emailaddress1
│ └── accountcategorycode
├── Page Size: 20
├── Include Total Count: Yes
└── Authorization: All authenticated users

Pagination Parameters

Paginated (Sliced) queries accept these query-string parameters. Internally they map to the Dataverse PagingInfo (cursor → paging cookie, page → page number, page-size → page size):

ParameterTypeDescription
pageIntegerPage number (1-based). Defaults to 1 when a page size is supplied without a page.
page-sizeIntegerRecords per page. Defaults to 20 when a page is supplied without a size.
cursorStringPaging cookie from the previous response; the primary continuation mechanism.

API Request

GET /sales/search-accounts?searchTerm=contoso&page=1&page-size=20

API Response

Success (200 OK):

{
"cursor": "eyJwYWdlIjoyfQ==",
"count": 20,
"totalRecordCount": 47,
"hasMoreRecords": true,
"data": [
{
"accountId": "a1-guid",
"name": "Contoso Corporation",
"accountNumber": "ACC-001",
"telephone1": "+1-555-0100",
"emailAddress1": "info@contoso.com",
"accountCategoryCode": 1
},
{
"accountId": "a2-guid",
"name": "Contoso Labs",
"accountNumber": "ACC-002",
"telephone1": "+1-555-0200",
"emailAddress1": "labs@contoso.com",
"accountCategoryCode": 2
}
]
}

Paginated Response Structure

FieldTypeDescription
cursorStringToken for retrieving next page
countIntegerNumber of records in current page
totalRecordCountIntegerTotal records matching query (if enabled)
hasMoreRecordsBooleanWhether more records exist
dataArrayThe actual records

Cursor-Based Navigation

To get the next page, include the cursor from the previous response:

GET /sales/search-accounts?cursor=eyJwYWdlIjoyfQ==

Query Configuration Options

Filter Expressions

Filters are configured as Dataverse QueryExpression conditions (attribute + operator + value), combined with And/Or logical operators and nested filters. Condition values can reference query input parameters. The full Dataverse ConditionOperator set is available; common operators include:

OperatorMeaning
EqualAttribute equals the value
NotEqualAttribute does not equal the value
GreaterThan / GreaterEqualNumeric/date greater than (or equal)
LessThan / LessEqualNumeric/date less than (or equal)
Like / NotLikePattern match (% wildcards)
Contains / DoesNotContainSubstring match — rewritten to Like/NotLike with the value wrapped as %value%
BeginsWith / EndsWithPrefix / suffix match
In / NotInValue is (not) in a set
Null / NotNullAttribute is (not) null
Between / NotBetweenValue falls (not) within a range
On / OnOrAfter / OnOrBefore, LastXDays, NextXDaysDate-relative operators
note

Filters are Dataverse query conditions, not SQL. The name = @param style shown in the configuration blocks above is illustrative shorthand for an Equal condition that references a parameter.

Column Expansion

For lookup fields, you can expand related entity fields:

Columns:
├── accountid
├── name
├── primarycontactid
│ └── Expand:
│ ├── fullname
│ ├── emailaddress1
│ └── jobtitle
└── ownerid
└── Expand:
└── fullname

Order By

Specify sort order for results:

Order By:
├── name ASC (alphabetical by name)
├── createdon DESC (newest first)
└── creditlimit DESC (highest credit first)

Access Control

Queries support the same Authorization Policy and Pre-conditions as actions:

Access Control:
├── Authorization Policy:
│ ├── Type: Demand Any
│ └── Roles:
│ ├── Administrator
│ ├── SalesManager
│ └── SalesRep

└── Pre-conditions:
└── Pre-condition 1:
├── Condition: @searchTerm IS NOT NULL OR @categoryCode IS NOT NULL
└── Error Message: "At least one search parameter is required"
(returns 400 CONDITION_VIOLATION)

Query Best Practices

Performance

  • Always include filters to limit result sets
  • Use indexes on filtered columns (coordinate with DBA)
  • Limit expanded relationships to necessary fields
  • Set appropriate page sizes (20-50 for UI lists)
  • Consider caching for frequently accessed, slowly-changing data

Security

  • Use authorization policies to restrict sensitive queries
  • Apply row-level filtering based on user context when needed
  • Don't expose internal IDs unnecessarily
  • Validate all input parameters

User Experience

  • Include total count for paginated queries when feasible
  • Return meaningful field names (use API Name mapping)
  • Sort by most relevant field by default
  • Support optional filtering for flexible searching