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 Type | Multiplicity | Description | Use Case |
|---|---|---|---|
| Single | Single | Returns exactly one record (or 204 No Content) | Get record by ID, get current user's profile |
| Multi | All | Returns all matching records as an array (no pagination) | Get all active statuses, get team members |
| Paginated | Sliced | Returns paged results with a paging cursor | List 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
| Scenario | Recommendation |
|---|---|
| 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):
| Parameter | Type | Description |
|---|---|---|
| page | Integer | Page number (1-based). Defaults to 1 when a page size is supplied without a page. |
| page-size | Integer | Records per page. Defaults to 20 when a page is supplied without a size. |
| cursor | String | Paging 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
| Field | Type | Description |
|---|---|---|
| cursor | String | Token for retrieving next page |
| count | Integer | Number of records in current page |
| totalRecordCount | Integer | Total records matching query (if enabled) |
| hasMoreRecords | Boolean | Whether more records exist |
| data | Array | The 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:
| Operator | Meaning |
|---|---|
Equal | Attribute equals the value |
NotEqual | Attribute does not equal the value |
GreaterThan / GreaterEqual | Numeric/date greater than (or equal) |
LessThan / LessEqual | Numeric/date less than (or equal) |
Like / NotLike | Pattern match (% wildcards) |
Contains / DoesNotContain | Substring match — rewritten to Like/NotLike with the value wrapped as %value% |
BeginsWith / EndsWith | Prefix / suffix match |
In / NotIn | Value is (not) in a set |
Null / NotNull | Attribute is (not) null |
Between / NotBetween | Value falls (not) within a range |
On / OnOrAfter / OnOrBefore, LastXDays, NextXDays | Date-relative operators |
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