Skip to main content

Queries

Queries are the read operations of your API. They define how data is retrieved from Dynamics 365 and exposed to consuming applications. FlowOn API supports three types of queries.

Query Types

Query TypeDescriptionUse Case
SingleReturns exactly one recordGet record by ID, get current user's profile
MultiReturns multiple records (no pagination)Get all active statuses, get team members
PaginatedReturns paged results with navigationList 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 /api/sales/queries/account?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 /api/sales/queries/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"
}
]

No Content (204): If no records match the query criteria, returns 204 No Content.

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

ParameterTypeDescription
pageIntegerPage number (1-based)
pageSizeIntegerRecords per page (max usually 100)
cursorStringCursor for cursor-based pagination

API Request

GET /api/sales/queries/search-accounts?searchTerm=contoso&page=1&pageSize=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 /api/sales/queries/search-accounts?cursor=eyJwYWdlIjoyfQ==

Query Configuration Options

Filter Expressions

Filters support standard comparison operators and can reference parameters:

OperatorExample
Equalsstatecode = 0
Not Equalsstatuscode != 2
Greater Thancreditlimit > 10000
Less Thancreatedon < @cutoffDate
Likename LIKE '%@searchTerm%'
Incategorycode IN (1, 2, 3)
Is Nullparentaccountid IS NULL
Is Not Nullprimarycontactid IS NOT NULL

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 Code: 400
└── Error Message: "At least one search parameter is required"

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