GraphQL Features
/businessnxtapi/features
section
Comprehensive GraphQL features - filtering, sorting, pagination, fragments, aliases, directives, error handling, unoptimized queries, batch requests.
2024-09-24T15:57:29+02:00
# GraphQL Features
Comprehensive GraphQL features - filtering, sorting, pagination, fragments, aliases, directives, error handling, unoptimized queries, batch requests.
Filtering
/businessnxtapi/features/filtering
page
API filtering enables precise data retrieval using logical operators like _gt, _lt, _like, _in, and _between. It supports complex queries and comparisons.
2025-04-15T09:48:42+02:00
# Filtering
API filtering enables precise data retrieval using logical operators like _gt, _lt, _like, _in, and _between. It supports complex queries and comparisons.
## Overview
Filtering is a key feature of the API. You can specify the filter as a parameter to a connection. Let's look at some examples:
In this first example, we fetch all the general ledger accounts that have the account number greater or equal than 6000 and lower than 7000.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(
filter: {_and :[
{accountNo :{_gte : 6000}},
{accountNo :{_lt : 7000}}
]}
)
{
totalCount
items
{
accountNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 46,
"items": [
{
"accountNo": 6000,
"name": "Avskr. bygn. og annen eiendom"
},
{
"accountNo": 6010,
"name": "Avskr. maskiner, inventar mv."
},
...
]
}
}
}
}
```
We can complicate the query a bit, and ask only for those general ledger accounts in the range 6000 - 7000 that start with "Leie". In this case, the filter would look as follows:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(
filter: {_and :[
{accountNo :{_gte : 6000}},
{accountNo :{_lt : 7000}},
{name :{_like:"Leie%"}}
]}
)
{
totalCount
items
{
accountNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 6,
"items": [
{
"accountNo": 6300,
"name": "Leie lokaler"
},
{
"accountNo": 6400,
"name": "Leie maskiner"
},
{
"accountNo": 6410,
"name": "Leie inventar"
},
{
"accountNo": 6420,
"name": "Leie datasystemer"
},
{
"accountNo": 6430,
"name": "Leie andre kontormaskiner"
},
{
"accountNo": 6440,
"name": "Leie transportmidler"
}
]
}
}
}
}
```
In the next example, we fetch all the associates that are either customers with the number in the range 10000 - 10010, or suppliers, with the number in the range 50000 - 50010.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid) {
associate (
filter:{
_or:
[
{
_and : [
{customerNo :{_gt:10000}},
{customerNo :{_lt:10010}}
]
},
{
_and : [
{supplierNo : {_gt:50000}},
{supplierNo : {_lt:50010}},
]
}
]
}
)
{
totalCount
items {
name
customerNo
supplierNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"totalCount": 18,
"items": [
{
"name": "YoYo Solutions AS",
"customerNo": 10001,
"supplierNo": 0
},
{
"name": "Spire Business",
"customerNo": 10002,
"supplierNo": 0
},
...
{
"name": "Scorpio Services Limited",
"customerNo": 0,
"supplierNo": 50001
},
{
"name": "Atlas Systems",
"customerNo": 0,
"supplierNo": 50002
},
...
]
}
}
}
}
```
Filtering is very similar to an open-source GraphQL implementation for Postgres databases. You can read more about that [here](https://hasura.io/docs/1.0/graphql/core/queries/query-filters.html#using-multiple-filters-in-the-same-query-and-or). This document can be used for finding more examples.
The filter for each table type (connection) has its own type. For instance, the filter type for the `Associate` table is called `FilterExpression_Associate`. You can see this if you explore the schema with GraphiQL, for instance:

`FilterExpression_Associate` type, in turn, has field for each column in the table. This fields are of a type called *_FilterClause*, such as `Int32_FilterClause`, `LimitedString_FilterClause`, etc. Two additional fields, `_and` and `_or`, of the same `FilterExpression_Associate` allow composing complex filter with the AND and OR logical operators.

The filter clause type, in turn, has fields representing operators, such as `_lt` or "lower than" or `_like` for the text comparison *like* operator.

The following table lists the available operators:
| Operator | Description |
| -------- | ----------- |
| `_eq` | equal to `value` |
| `_gt` | greater than `value` |
| `_gte` | greater than or equal to `value` |
| `_in` | in list of values |
| `_is_not_null` | is not `null` |
| `_is_null` | is `null` |
| `_is_off` | is `off` - available for bit field columns only |
| `_is_on` | is `on` - available for bit field columns only |
| `_lt` | less than `value` |
| `_lte` | less than or equal to `value` |
| `_like` | like `value` |
| `_not_eq` | not equal `value` |
| `_not_in` | not in list of values |
| `_not_like` | not like `value` |
| `_between` | between `from` and `to` (inclusive bounds) |
| `_not_between` | not between `from` and `to` (exclusive bounds) |
The filter can also be provided as an argument to the query. The filter value is an object of the filter expression type. This is shown in the following example:
```graphql { title = "Query" }
query GetGLAs(
$companyNo: Int,
$filter : FilterExpression_GeneralLedgerAccount)
{
useCompany(no: $companyNo)
{
generalLedgerAccount(filter: $filter)
{
totalCount
items
{
accountNo
name
}
}
}
}
```
```graphql { title = "Variables" }
{
"companyNo": 9112233,
"filter": {"_and" :[
{"accountNo" :{"_gte" : 6000}},
{"accountNo" :{"_lt" : 7000}}]}
}
```
## `_in` and `_not_in` operators
Most of the operators take a single value. The `_in` and `_not_in` operators, however, take an array of values, as show in the following example:
```graphql
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate (filter : {customerNo : {_in: [10001,10002,10003]}})
{
totalCount
items
{
name
associateNo
customerNo
supplierNo
}
}
}
}
```
These two operators (`_in` and `_not_in`) are helpful for simplifying more complex filters. The following two tables show a filter using these two operators, and the equivalent expression without them.
```graphql { title = "With_in" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate (filter : {
customerNo : {_in: [10001,10002,10003]}})
{
totalCount
items
{
name
associateNo
customerNo
supplierNo
}
}
}
}
```
```graphql { title = "Without_in" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate (filter : {_or: [
customerNo : {_eq: 10001},
customerNo : {_eq: 10002},
customerNo : {_eq: 10003}
]})
{
totalCount
items
{
name
associateNo
customerNo
supplierNo
}
}
}
}
```
```graphql { title = "With_in" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate (filter : {
customerNo : {_not_in: [10001,10002,10003]}})
{
totalCount
items
{
name
associateNo
customerNo
supplierNo
}
}
}
}
```
```graphql { title = "Without_in" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate (filter : {_and: [
customerNo : {_not_eq: 10001},
customerNo : {_not_eq: 10002},
customerNo : {_not_eq: 10003}
]})
{
totalCount
items
{
name
associateNo
customerNo
supplierNo
}
}
}
}
```
If the argument for the `_in` or `_not_in` operators is an empty array, the result will be an empty set.
An example is shown below, where we first query the `Order` table, exporting the value of the `sellerOrBuyer` column to the variable `sellers`. We then query the `Associate` table, filtering for the employees with the numbers in the `sellers` array. Because no order matches the filter, the result is an empty `sellers` array, which results in no data being returned.
```graphql { title = "Query" }
query find_selers($cid: Int!, $sellers: [Int!] = [])
{
useCompany(no: $cid)
{
order(
filter: {sellerOrBuyer: {_gt: 999}},
first: 5)
{
items
{
sellerOrBuyer @export(as: "sellers")
}
}
associate(
filter: {employeeNo: {_in: $sellers}})
{
items
{
employeeNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"order": {
"items": null
},
"associate": {
"items": null
}
}
}
}
```
If the `_in` or `_not_in` operators are used in an `AND` expression, and the argument is an empty array, the resulting filter will be an empty expression, and no data will be returned.
```graphql
order(
filter: {_and: [
{changeDateTime : {_gte: {"2024-01-24T12:00:00"}}}
{orderNo: {_in: []}}
]})
{
items
{
orderNo
dueDate
}
}
```
This results in an empty filter. No data is returned when a filter is provided but it is empty. On the other hand, when no filter is provided, all the data is returned.
If the `_in` or `_not_in` operators are used in an `OR` expression and the argument is an empty array, the subexpression is removed from the filter expression, which may still produce a valid filter that can return data.
```graphql { title = "Filter" }
filter: {_or: [
{changeDateTime :
{_gte: {"2024-01-24T12:00:00"}}}
{orderNo: {_in: []}}
]}
```
```graphql { title = "Equivalent" }
filter: {_or: [
{changeDateTime :
{_gte: {"2024-01-24T12:00:00"}}}
]}
```
## `_between` / `_not_between` operators
These two operators require two values that define the lower and upper bounds of the range. The range is inclusive, meaning that the values that are equal to the lower or upper bound are included in the result.
```graphql
query read_glas($cid : Int!, $pagesize : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(
first: $pagesize,
filter : {accountNo : {_between : {from: 1000, to: 2000}}})
{
totalCount
items
{
accountNo
name
}
}
}
}
```
These operators are support for fields of the following types:
- numeric (`Int`, `Long`, `Decimal`, etc.)
- Date
- Time
- DateTime
The following example shows an expression using both the `_between` operator (for an int field) and the `_not_between` operator (for a datetime field):
```graphql
query read_glas($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(filter: { _and : [
{accountNo : {
_not_between : {
from : 3000,
to : 5000
}
}},
{changedDateTime : {
_between : {
from : "2024-05-01T12:00:00",
to : "2024-05-30T23:59:59"
}
}}
]})
{
totalCount
items
{
accountNo
name
}
}
}
}
```
## Comparing with current date/time
The model define various fields for date and time. All the nodes that have the type `Date_FilterClause`, `Time_FilterClause`, or `DateTime_FilterClause` can be compared with the current date/time. These GraphQL types, have, in addition for fields defining operators, an additional field called `_cmp` whose type is `ReferenceDateTime_FilterClause`, which looks as follows:

This type features nodes for all the comparison operators available for date and time fields. However, their type is `ReferenceDateTime` that has the following fields:
| Name | Type | Description |
| ---- | ---- | ----------- |
| `day` | `ReferenceDays` | The day this moment refers to. One of `YESTERDAY`, `TODAY`, or `TOMORROW`. |
| `dayOffset` | `Int` | The number of days to offset from the current date. For instance `TODAY` with an offset of one is `TOMORROW`, and `TODAY` with an offset of -1 is `YESTERDAY`. |
| `time` | `ReferenceTimePoints` | Can only have the value `NOW`. |
| `minuteOffset` | `Int` | The number of minutes to offset from the current time. |
The following example shows a query that fetches all orders that where changed in the last hour:
```graphql
query read_orders ($cid: Int)
{
useCompany(no: $cid)
{
order(filter :
{
_and : [
{
changedDateAsDate : {
_cmp : {
_eq : {day : TODAY, dayOffset : 0}
}
}
},
{
changedTimeAsTime : {
_cmp : {
_gte : {time : NOW, minuteOffset: -60}
}
}
}
]
})
{
totalCount
items
{
orderNo
}
}
}
}
```
This particular query can be simplified by using the `createdDateTime` field, as shown bellow:
```graphql
query read_orders ($cid: Int)
{
useCompany(no: $cid)
{
order(filter :
{
createdDateTime : {
_cmp : {
_gte : {
day : TODAY,
time : NOW,
minuteOffset : -60}
}
}
})
{
totalCount
items
{
orderNo
}
}
}
}
```
## Having expressions
The same operators described here for filters can be used for `having` clauses in queries that include aggregations.
To learn more about this, see [Grouping: The `having` argument](../schema/queries/grouping.md#the-having-argument).
Sorting
/businessnxtapi/features/sorting
page
API documentation explaining how to specify custom sorting of query results using the orderBy parameter in GraphQL queries.
2025-04-15T09:48:42+02:00
# Sorting
API documentation explaining how to specify custom sorting of query results using the orderBy parameter in GraphQL queries.
For every query, you can specify a sorting expression through the `orderBy` parameter on each connection. Every connection has the `orderBy` parameter, although its type differs for each connection. The name format is `OrderBy_`. Therefore, the sort order types have names such as `OrderBy_Associate` or `OrderBy_GeneralLedgerAccount`. This sort order type contains fields for all the columns of the table, except for the in-memory ones which cannot be searched or sorted. The type of all these fields is called `SortOrder` and is an enumeration with two values: `ASC` for asceding order and `DESC` for descending order. Therefore, this makes it possible to sort the results ascending or desceding by any column in the table.
The following image shows a partial view of the `OrderBy_Associate` type of the `orderBy` parameter of the `UseCompany_Associate_Connection` connection.

Here is an example of a query for the first 10 customers from the associates table sorted descending by their name.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(first: 10,
filter:{customerNo:{_not_eq:0}},
orderBy:[{name:DESC}])
{
totalCount
items
{
associateNo
customerNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"totalCount": 319,
"items": [
{
"associateNo": 286,
"customerNo": 10285,
"name": "Yggdrasil Ltd."
},
{
"associateNo": 12,
"customerNo": 10011,
"name": "Wilbur Andersen"
},
{
"associateNo": 285,
"customerNo": 10284,
"name": "Werner Systems AS"
},
...
]
}
}
}
}
```
As you can see, the `orderBy` is an array of values which makes it possible to define multiple sorting columns. They are considered in the give order.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
postalAddress(
first :5,
orderBy: [
{postalArea:ASC},
{postCode:ASC}
]
)
{
totalCount
items
{
postCode
postalArea
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"postalAddress": {
"totalCount": 53,
"items": [
{
"postCode": "0001",
"postalArea": "Bergen"
},
{
"postCode": "0010",
"postalArea": "Bergen"
},
{
"postCode": "0015",
"postalArea": "Bergen"
},
{
"postCode": "0018",
"postalArea": "Oslo"
},
{
"postCode": "0021",
"postalArea": "Oslo"
}
]
}
}
}
}
```
The sort order can also be passed as a variable. Here is an example:
```graphql { title = "Query" }
query read($cid : Int!,
$size : Int!,
$order : [OrderBy_PostalAddress]!)
{
useCompany(no: $cid)
{
postalAddress(
first :$size,
orderBy: $order)
{
totalCount
items
{
postCode
postalArea
}
}
}
}
```
```graphql { title = "Variables" }
{
"cid": 9112233,
"size": 10,
"order": [
{"postalArea" : "ASC"},
{"postCode" :"ASC"}
]
}
```
## Deprecated sorting
The sorting order can be specified with the `sortOrder` argument, is similar to `orderBy`, except that it is a single object instead of an array.
> [!WARNING]
>
> The `sortOrder` argument is deprecated and will be removed in the future. Therefore, it is recommended to use the `orderBy` argument instead.
When you need to specify more than one column for sorting, the only way to do it when using `sortOrder` is by using the composition field `_thenBy`. This is exemplified in the next query, where we fetch the first 10 postal addresses, sorted first by postal area, asceding, and then by postal code, also ascending.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
postalAddress(
first :5,
sortOrder:{
postalArea:ASC
_thenBy : {
postCode:ASC}})
{
totalCount
items
{
postCode
postalArea
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"postalAddress": {
"totalCount": 53,
"items": [
{
"postCode": "0001",
"postalArea": "Bergen"
},
{
"postCode": "0010",
"postalArea": "Bergen"
},
{
"postCode": "0015",
"postalArea": "Bergen"
},
{
"postCode": "0018",
"postalArea": "Oslo"
},
{
"postCode": "0021",
"postalArea": "Oslo"
}
]
}
}
}
}
```
Any number or fields can be chained to define the sort order in this manner. However, only one table column per level can be specified without the `_thenBy` field. Otherwise, an error message is returned and the result wil be sorted in the alphabetical order of the specified columns.
```graphql { title = "Query" }
query read($cid: Int!)
{
useCompany(no :$cid)
{
postalAddress(
first : 3,
sortOrder:
{
postalArea:ASC
postCode:ASC
})
{
totalCount
items
{
postCode
postalArea
}
}
}
}
```
```graphql { title = "Result" }
{
"errors": [
{
"message": "More than one field is specified in one sort clause (postCode,postalArea). The result may be incorrect. Use the _thenBy field to chain multiple table fields in the sort order."
}
],
"data": {
"useCompany": {
"postalAddress": {
"totalCount": 5093,
"items": [
{
"postCode": "0001",
"postalArea": "OSLO"
},
{
"postCode": "0010",
"postalArea": "OSLO"
},
{
"postCode": "0015",
"postalArea": "OSLO"
}
]
}
}
}
}
```
Pagination
/businessnxtapi/features/pagination
page
Pagination uses relay style, supporting forward and backward navigation.
2025-04-15T09:48:42+02:00
# Pagination
Pagination uses relay style, supporting forward and backward navigation.
## Overview
Pagination is implemented in the relay style. Reference documentation can be found [here](https://relay.dev/graphql/connections.htm). Both forward and backward pagination are supported:
- the arguments for forward pagination are `first` (specifing the maximum number of items to return) and `after` (which is an opaque cursor, typically pointing to the last item in the previous page)
- the arguments for backward pagination are `last` (specifying the maximum number of items to return) and `before` (which is an opaque cursor, typically pointing to the first item in the next page)
- a `skip` argument is available for both forward and backward pagination and specifies a number of records to be skipped (from the given cursor) before retrieving the requested number of records.
> [!NOTE]
>
> The values for `first`, `last`, and `skip` must be positive integers.
> [!WARNING]
>
> Pagination does not work with grouping (`groupBy` and `having` arguments).
Information about a page is returned in the `pageInfo` node. This contains the following fields:
| Field | Description |
| ----- | ----------- |
| `hasNextPage` | Indicates whether there are more records to fetch after the current page. |
| `hasPreviousPage` | Indicates whether there are more records to fetch before the current page. |
| `startCursor` | An opaque cursor pointing to the first record in the current page. |
| `endCursor` | An opaque cursor pointing to the last record in the current page. |
When forward pagination is performed, the `hasNextPage` field is `true` if there are more records to fetch after the current page. When backward pagination is performed, the `hasNextPage` field is `true` if there are more records to fetch before the current page.
Similarly, when forward pagination is performed, the `hasPreviousPage` field is `true` if there are more records to fetch before the current page. When backward pagination is performed, the `hasPreviousPage` field is always `false`.
## Understanding pagination
In the following example, we fetch the first 5 associtates. This being the first page, there is no argument for the `after` parameter. Passing a `null` value for the `after` parameter is equivalent to not passing it at all.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(first: 5)
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 1,
"name": "Et Cetera Solutions"
},
{
"associateNo": 2,
"name": "Rock And Random"
},
{
"associateNo": 3,
"name": "Handy Help AS"
},
{
"associateNo": 4,
"name": "EasyWay Crafting"
},
{
"associateNo": 5,
"name": "Zen Services AS"
}
]
}
}
}
}
```
For a following page, we need to take the value of the `endCursor` return from a query and supply it as the argument to `after`. This is shown in the following example, where `"NQ=="` from the query above was supplied for the `after` parameter:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(first: 5, after: "NQ==")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "Ng==",
"endCursor": "MTA="
},
"items": [
{
"associateNo": 6,
"name": "Smart Vita AS"
},
{
"associateNo": 7,
"name": "Full Force Services"
},
{
"associateNo": 8,
"name": "HomeRun Auto AS"
},
{
"associateNo": 9,
"name": "Trade Kraft AS"
},
{
"associateNo": 10,
"name": "Nodic Cool Sports AS"
}
]
}
}
}
}
```
Requests with backwards pagination are performed similarly, exept that `last` and `before` are used instead of `first` and `after`. In the following example, the value of `before` is taken from the value of `startCursor` returned by the previous query that returned the second page of associates (with five records per page). As a result, the data returned from this new query is actually the first page of associates.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(last: 5, before: "Ng==")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 1,
"name": "Et Cetera Solutions"
},
{
"associateNo": 2,
"name": "Rock And Random"
},
{
"associateNo": 3,
"name": "Handy Help AS"
},
{
"associateNo": 4,
"name": "EasyWay Crafting"
},
{
"associateNo": 5,
"name": "Zen Services AS"
}
]
}
}
}
}
```
Providing both forward and backward pagination arguments (`first`/`after` and `last`/`before`) is illegal and the query will fail with an error.
If pagination arguments are not supplied a default page size of 5000 records is used.
When you fetch data in pages (which is recommended for most scenarious) you can fetch the total number of objects in the table with disregard to the page that is fetched or the size of the page. This is possible with the `totalCount`. This will return the number of records that match the filter (if any) but ignoring paginagion (if any).
This feature is exemplified below. We, again, fetch the first 5 associates that have a customer number greater or equal than 11000 but also request the total number of records that match this filter.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate(
first: 5,
filter: {customerNo :{_gte: 11000}})
{
totalCount
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
customerNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"totalCount": 29,
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "NQ=="
},
"items": [
{
"associateNo": 308,
"customerNo": 11000,
"name": "Auto Glory AS"
},
{
"associateNo": 309,
"customerNo": 11001,
"name": "Home Team Business"
},
{
"associateNo": 310,
"customerNo": 11003,
"name": "Corpus Systems"
},
{
"associateNo": 311,
"customerNo": 11004,
"name": "Dash Credit AS"
},
{
"associateNo": 312,
"customerNo": 11007,
"name": "Smart Help Services AS"
}
]
}
}
}
}
```
You can skip a number of records before feetching a page (with the size indicated by either the `first` or the `last` arguments) using the argument `skip`. This is an optional argument. When present, it indicates the number of records to be skipped. Here is an example:
```graphql { title = "Forward" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(skip : 5,
first: 10)
{
totalCount
items
{
accountNo
}
}
}
}
```
```graphql { title = "Backward" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(skip : 5,
last : 10,
before: "MTE=")
{
totalCount
items
{
accountNo
}
}
}
}
```
> [!WARNING]
>
> Because the execution of queries for joined tables is optimized for performance, pagination does not work as described in this document. To understand the problem and the workaround see [Unoptimized queries](./unoptimized.md).
## Pagination in depth
To understand how pagination works, we will consider the following example, where the general ledger account table contains 25 records. It could look like this:
```
page 1 (10 records) page 2 (10 records) page 3 (5 records)
|---------------------------------|---------------------------------|---------------------------------|
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
```
### Forward pagination
To fetch the records from the beginning, we perform a query like this:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: null)
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MQ==",
"endCursor": "MTA="
},
"items": [
{
"accountNo": 1000
},
{
"accountNo": 1020
},
...
{
"accountNo": 1100
},
{
"accountNo": 1120
}
]
}
}
}
}
```
This returns the first ten records, from index 1 to 10, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set.
```
page 1 (10 records)
|---------------------------------|
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
```
To fetch the next page of 10 records, we provide the value of `endCursor` as the argument for `after`:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: "MTA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "MTE=",
"endCursor": "MjA="
},
"items": [
{
"accountNo": 1130
},
{
"accountNo": 1140
},
...
{
"accountNo": 1240
},
{
"accountNo": 1250
}
]
}
}
}
}
```
This returns the second page of ten records, from index 11 to 20, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set.
```
after page 2 (10 records)
| |---------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
```
To fetch the next page of 10 records, we repeat the operation, again, using the `endCursor` for the value of the `after` argument:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
after: "MjA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": false,
"hasPreviousPage": true,
"startCursor": "MjE=",
"endCursor": "MjU="
},
"items": [
{
"accountNo": 1260
},
{
"accountNo": 1270
},
{
"accountNo": 1280
},
{
"accountNo": 1300
},
{
"accountNo": 1310
},
]
}
}
}
}
```
This returns a page of only 5 records, from index 21 to 25, and the `startCursor` and `endCursor` are pointing to the first and last elements in the set.
```
after page 3 (5 records)
| |----------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
```
The value of the `hasNextPage` field is `false` because there are no more records to fetch.
> [!NOTE]
>
> The value used for the `after` argument should be the value of the `endCursor` from the previous page (unless it's `null`, in which case it means the fetching should start from the beginning). The `after` cursor identifies the position of the last record in a (previous) page. The record at the position represented by the cursor passed to the `after` argument is not included in the result set. A page starts with the record following the one identified by `after`.
It is also possible to skip records before fetching a page. This is done by providing the `skip` argument. The following example skips the first 3 records before fetching a page of 10 records. We don't start from the beginning, but at the end of the first page of 10 records, as exemplify below:
```
after skip 3 records page of 10 records
| >-------------------<|-----------------------------------------------|
v
| 1000 | 1020 | ... | 1100 | 1120 | 1130 | 1140 | 1150 | 1160 | ... | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
```
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(first : 10,
skip : 3,
after: "MTA=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "MTQ=",
"endCursor": "MjM="
},
"items": [
{
"accountNo": 1160
},
{
"accountNo": 1200
},
...
{
"accountNo": 1270
},
{
"accountNo": 1280
}
]
}
}
}
}
```
### Backward pagination
Backward pagination works similarly, except that the `last` and `before` arguments are used instead of `first` and `after`. The argument `last` indicates how many records the page should contain. The argument `before` represents the position of the record before which the page is located. The record at the `before` position is not included in the result set.
Therefore, if in the preceding example we fetched all the records in the table, and now want to move backwards, but use the value of the `endCursor` as the arguement for `before`, then this last record will not be included in the returned page.
```
page of 10 records before
|---------------------------------------------------------------------| |
v
| 1000 | 1020 | ... | 1160 | 1200 | 1210 | 1220 | 1230 | 1240 | 1250 | 1260 | 1270 | 1280 | 1300 | 1310 |
^ ^
| |
startCursor endCursor
```
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
generalLedgerAccount(last : 10,
before: "MjU=")
{
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
accountNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MTU=",
"endCursor": "MjQ="
},
"items": [
{
"accountNo": 1200
},
{
"accountNo": 1210
},
...
{
"accountNo": 1280
},
{
"accountNo": 1300
}
]
}
}
}
}
```
Fragments
/businessnxtapi/features/fragments
page
Documentation on using and nesting fragments in GraphQL queries to efficiently construct reusable sets of fields.
2025-04-15T09:48:42+02:00
# Fragments
Documentation on using and nesting fragments in GraphQL queries to efficiently construct reusable sets of fields.
You can build fragments to construct sets of fields and then include them in queries where you need to. Nested fragments are supported.
Here is an example of a query constructed using a fragment called `associateBasic`, which in turn uses two other fragments, `associateContact` and `associateAddress`.
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no: $cid)
{
associate
{
items
{
...associateBasic
customerNo
supplierNo
}
}
}
}
fragment associateBasic on Associate
{
name
userName
... associateContact
... associateAddress
}
fragment associateContact on Associate
{
emailAddress
phone
}
fragment associateAddress on Associate
{
addressLine1
postCode
postalArea
countryNo
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"items": [
{
"name": "ABB Installasjon AS",
"userName": "",
"emailAddress": "",
"phone": "12345678",
"addressLine1": "Ole Deviks Vei 10",
"postCode": "1375",
"postalArea": "Billingstad",
"countryNo": 0,
"customerNo": 10000,
"supplierNo": 0
},
{
"name": "ABB Kraft AS",
"userName": "",
"emailAddress": "",
"phone": "12345678",
"addressLine1": "Jacob Borchs Gate 6",
"postCode": "3002",
"postalArea": "Drammen",
"countryNo": 0,
"customerNo": 10001,
"supplierNo": 0
}
]
}
}
}
}
```
Named queries and parameters
/businessnxtapi/features/parameters
page
GraphQL supports named queries and variables, enhancing query clarity. Example includes defining and utilizing variables for efficient data retrieval.
2024-09-24T15:57:29+02:00
# Named queries and parameters
GraphQL supports named queries and variables, enhancing query clarity. Example includes defining and utilizing variables for efficient data retrieval.
GraphQL supports variables and naming queries, which should always be used since they make the query more clear while reading.
Let's look at an example. In the following snippet, `GetCustomers` is a named query that returns a page associates. This query has three parameters: `$companyNo` specifies the Visma.net company number that uniquely identifies the company within the system, `$pageSize` the maximum number of records to be returned, and `$after` the cursor that indicates the position after which the records are to be fetched. If nothing is specified, this means the first `$pageSize` records are to be fetched.
```graphql
query GetCustomers($companyNo: Int, $pageSize: Int, $after :String)
{
useCompany(no: $companyNo)
{
associate(first: $pageSize, after: $after)
{
totalCount
pageInfo
{
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items
{
associateNo
customerNo
name
}
}
}
}
```
If you use GraphiQL, you can define the variables in the query variable pane in the lower left of the IDE, as shown in the following image:

When you perform the request to GraphQL programmatically, or from a tool that does not directly support GraphQL variables, you must put the request containing both the query and variables in the body, as `application/json`.
```json
{
"query" : "query GetCustomers($companyNo: Int, $pageSize: Int, $after :String)\n{\n useCompany(no: $companyNo)\n {\n associate(first: $pageSize, after: $after)\n {\n totalCount\n pageInfo\n {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items\n {\n associateNo\n customerNo\n name\n }\n }\n }\n}",
"variables":"{\"companyNo\": 9112233,\"pageSize\": 5}"
}
```
Details about the raw form of a GraphQL query were presented in an earlier section of this tutorial.
Aliases
/businessnxtapi/features/aliases
page
GraphQL API documentation on using aliases to rename fields and query the same field multiple times with different arguments.
2025-04-15T09:48:42+02:00
# Aliases
GraphQL API documentation on using aliases to rename fields and query the same field multiple times with different arguments.
GraphQL allows you to rename the result of a field to anything you want. This is possible with the help of [aliases](https://graphql.org/learn/queries/#aliases).
You can use this feature to:
- rename long fields and use name that you prefer over fields from the schema
- query for the same field multiple times but with different arguments
Here is an example:
```graphql
query GetPaymentLines($cid: Int!)
{
useCompany(no: $cid)
{
paymentLine(first: 1)
{
items {
paymentNo
lineNo
currency : joinup_Currency {
isoCode
}
supplier : joinup_Associate_via_Supplier {
bankAccount
country : joinup_Country {
isoCode
}
}
}
}
}
}
```
Aliases can be used in:
- queries (example shown above)
- inserts and updates
- aggregates
Here is another example in an aggregate function:
```graphql
query GetAgregates($cid: Int!)
{
useCompany(no: $cid)
{
order_aggregate
{
totalvat : sum
{
vatAmountDomestic
}
}
}
}
```
As previously mentioned, an important use case is to query for the same field multiple times but using different arguments. An example is shown below:
```graphql { title = "Query" }
query ($cid : Int!)
{
useCompany(no : $cid)
{
some : generalLedgerAccount(first : 2)
{
items
{
accountNo
name
}
}
more : generalLedgerAccount(first : 2,
filter : {accountNo : {_gt : 6000}})
{
items
{
accountNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"some": {
"items": [
{
"accountNo": 1000,
"name": "Forskning og utvikling"
},
{
"accountNo": 1020,
"name": "Konsesjoner"
}
]
},
"more": {
"items": [
{
"accountNo": 6010,
"name": "Avskr. maskiner, inventar mv."
},
{
"accountNo": 6020,
"name": "Avskr. immaterielle eiendeler"
}
]
}
}
},
"extensions": {
"vbnxt-trace-id": "02c029a3a07c7a..."
}
}
```
Directives
/businessnxtapi/features/directives
page
GraphQL directives modify query execution, including core directives like @include and @skip, and custom ones like @export.
2025-04-15T09:48:42+02:00
# Directives
GraphQL directives modify query execution, including core directives like @include and @skip, and custom ones like @export.
Directives are a GraphQL feature that affect the execution of a query in any way the server desires. Directives can be attached to different parts of the schema (field, fragment inclusion, etc.). There are several core GraphQL directives:
| Directive | Attached to | Description |
| --------- | ----------- | ----------- |
| `@include` | field, fragment inclusion | Only include this field in the result if the argument is true. |
| `@skip` | field, fragment inclusion | Skip this field if the argument is true. |
In addition, we provide custom directives:
| Directive | Attached to | Description |
| --------- | ----------- | ----------- |
| `@export` | field, fragment inclusion | Export the value of a field into a variable that can be used somewhere else in the query. |
| `@dependsOn` | field, fragment inclusion | Specify that a field depends on another field. |
## The `@include` directive
Includes a field or fragment in the result only if the Boolean argument is `true`.
**Syntax**:
```graphql
@include(if: Boolean!)
```
**Example**:
```graphql
query($cid : Int!, $pagesize : Int, $withdetails : Boolean!)
{
useCompany(no : $cid)
{
order(first : $pagesize)
{
totalCount
items
{
orderNo
orderDate
lines : joindown_OrderLine_via_Order(first: 2) @include(if: $withdetails)
{
totalCount
items
{
lineNo
transactionDate
}
}
}
}
}
}
```
**Result**:
```graphql { title = "$withdetails is false" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 451,
"items": [
{
"orderNo": 1,
"orderDate": 20210212
},
{
"orderNo": 2,
"orderDate": 20130203
}
]
}
}
}
}
```
```graphql { title = "$withdetails is true" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 451,
"items": [
{
"orderNo": 1,
"orderDate": 20210212,
"lines": {
"totalCount": 6,
"items": [
{
"lineNo": 1,
"transactionDate": 0
},
{
"lineNo": 2,
"transactionDate": 0
}
]
}
},
{
"orderNo": 2,
"orderDate": 20130203,
"lines": {
"totalCount": 5,
"items": [
{
"lineNo": 1,
"transactionDate": 20140904
},
{
"lineNo": 2,
"transactionDate": 20140904
}
]
}
}
]
}
}
}
}
```
## The `@skip` directive
Skips a field if the Boolean argument is `true`.
**Syntax**:
```graphql
@skip(if: Boolean!)
```
**Example**:
```graphql
query($cid : Int!, $pagesize : Int, $nodetails : Boolean!)
{
useCompany(no : $cid)
{
order(first : $pagesize)
{
totalCount
items
{
orderNo
orderDate
lines : joindown_OrderLine_via_Order(first: 2) @skip(if: $nodetails)
{
totalCount
items
{
lineNo
transactionDate
}
}
}
}
}
}
```
**Result**:
```graphql { title = "$nodetails is true" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 451,
"items": [
{
"orderNo": 1,
"orderDate": 20210212
},
{
"orderNo": 2,
"orderDate": 20130203
}
]
}
}
}
}
```
```graphql { title = "$nodetails is false" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 451,
"items": [
{
"orderNo": 1,
"orderDate": 20210212,
"lines": {
"totalCount": 6,
"items": [
{
"lineNo": 1,
"transactionDate": 0
},
{
"lineNo": 2,
"transactionDate": 0
}
]
}
},
{
"orderNo": 2,
"orderDate": 20130203,
"lines": {
"totalCount": 5,
"items": [
{
"lineNo": 1,
"transactionDate": 20140904
},
{
"lineNo": 2,
"transactionDate": 20140904
}
]
}
}
]
}
}
}
}
```
## The `@export` directive
The `@export` directive in GraphQL exports the value of a field into a variable that is used somewhere else in the query. This can be either a single value or an array.
**Syntax**:
```graphql
@export(as: "variablename", distinct : true)
```
**Example**: Fetch the cutomer number of the associate whose indentifier is specified and then use the customer number to fetch orders.
```graphql { title = "Query" }
query($cid : Int!,
$ano : Int!,
$pagesize: Int,
$customerId : Int = 0)
{
useCompany(no: $cid)
{
associate(filter : {associateNo : {_eq: $ano}})
{
items
{
customerNo @export(as: "customerId")
}
}
order(first : $pagesize,
filter : {customerNo : {_eq : $customerId}})
{
totalCount
items
{
orderNo
orderDate
customerNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"associate": {
"items": [
{
"customerNo": 10000
}
]
},
"order": {
"totalCount": 14,
"items": [
{
"orderNo": 81,
"orderDate": 20150115,
"customerNo": 10000
}
]
}
}
}
}
```
**Example**: Add one order and two order lines for the order with a single request.
```graphql { title = "Query" }
mutation ($cid: Int,
$cno : Int,
$pid1 : String,
$pid2 : String,
$orderId: Int = 0)
{
useCompany(no: $cid)
{
order_create(
values: [
{
orderDate: 20221104,
customerNo: $cno,
orderType: 1,
transactionType: 1
}
]
)
{
affectedRows
items
{
orderNo @export(as: "orderId")
}
}
orderLine_create(
values: [
{
orderNo: $orderId,
productNo: $pid1,
quantity: 1
},
{
orderNo: $orderId,
productNo: $pid2,
quantity: 2
}
]
)
{
affectedRows
items
{
lineNo
orderNo
productNo
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"order_create": {
"affectedRows": 1,
"items": [
{
"orderNo": 632
}
]
},
"orderLine_create": {
"affectedRows": 2,
"items": [
{
"lineNo": 1,
"orderNo": 632,
"productNo": "1001"
},
{
"lineNo": 2,
"orderNo": 632,
"productNo": "1002"
}
]
}
}
}
}
```
There are several things to keep in mind when using this directive:
- The variable into which the field value is exported must be defined in the query parameter list. Otherwise, when you use the variable later on in another part of the query, the server will complain that it is not defined.
- You can only export the value of one field into one variable. If you attempt to write values from multiple fields into the same variable they will be overwritten based of the order of evaluation.
- If you export multiple values into the same variable, the last field that is evaluated will define the value of the variable.
- If the variable is defined as an array, you can store multiple values.
In the previous examples, we have used a variable that could store a single value. Therefore, if a query returned multiple elements, a field would get evaluated multiple times and each time the variable would be overritten. The value from the last evaluation is the one that is finally stored in the variable.
However, all the values can be preserved in an array. The only change is that you need to define the variable of an array type. Moreover, you can use the Boolean optional argument `distinct` to retain only the distict values and discard duplicates. An array variable can be used for instance with the `_in` and `_not_in` filters.
The following example shows a query that fetches information about all the orders that have lines that were updated after a given moment in time:
```graphql
query read_modified_orders($cid : Int!, $dt : DateTime,
$ono : [Int] = [])
{
useCompany(no : $cid)
{
orderLine(filter : {changedDateTime : {_gt : $dt}})
{
items
{
orderNo @export(as : "ono", distict : true)
}
}
order(filter : {orderNo : {_in : $ono})
{
items
{
orderNo
orderDate
customerNo
}
}
}
}
```
## The `@dependsOn` directive
The `@dependsOn` directive in GraphQL specifies that a field depends on another field. This directive can be used in the rare situations when fields are executed out of order due. Typically, fields are executed in the order they are defined in the query. However, in order to optimize the query execution, some requests are packed together before being sent to the back-end. This changes the order of execution as some fiels defined later in the query are executed before fields defined earlier. Moreoever, some fields depend on others because they use a variable set from an earlier field with the use of the `@export` directive.
Use the `@dependsOn` directive to ensure that a field is executed only after a previous field was executed.
**Syntax**:
```graphql
@dependsOn(field: "name")
```
**Example**:
```graphql
mutation CreateBatchWithAttachment ($cid: Int,
$batchId: Int = 0,
$fina: String,
$fiby: String,
$tok: String)
{
useCompany(no: $cid) {
# create the batch
batch_create(
values: {
voucherSeriesNo: 1,
valueDate: 20250121
description: "Demo batch"
}
)
{
affectedRows
items {
batchNo @export(as: "batchId")
}
}
# create the voucher
voucher_create(
values: {
batchNo: $batchId
voucherDate: null
voucherType: 21
voucherNo: null
debitAccountNo: 5000
creditAccountNo: 1920
amountDomestic: 500.00
text: "first voucherline"
}
)
{
affectedRows
items {
batchNo
voucherNo
}
# add a document to the batch
voucher_processings {
addNewDocument(
filter: {batchNo: {_eq: $batchId}},
args: {
fileName: $fina,
fileBytes: $fiby
}
)
{
succeeded
}
}
# upload the document to the file service
incomingAccountingDocumentAttachment_processings {
uploadToFileService(
filter: {fileName: {_eq: $fina}},
args: {connectToken: $tok}
) @dependsOn(field: "addNewDocument")
{
succeeded
}
}
}
}
```
## References
You can learn more about the core GraphQL directives from these articles:
- [Directives](https://graphql-dotnet.github.io/docs/getting-started/directives/)
- [GraphQL Directives](https://spec.graphql.org/October2021/#sec-Type-System.Directives)
Error handling
/businessnxtapi/features/errors
page
GraphQL API error handling - status codes, error messages, syntax errors, multiple request outcomes, execution tracing for troubleshooting, and detailed error property explanations.
2025-04-15T09:48:42+02:00
# Error handling
GraphQL API error handling - status codes, error messages, syntax errors, multiple request outcomes, execution tracing for troubleshooting, and detailed error property explanations.
A GraphQL query is an HTTP POST request with the content type `application/json` and the body having the form:
```json
{
"query" : "...",
"variables" : "..."
}
```
If the authorization for a request fails (no authorization header, expired token, etc.) the return status code is `401` (`Unauthorized)` and the response body is the following:
```html
401 Authorization Required
401 Authorization Required
nginx
```
However, for most requests, whether they are successful or they failed, the return status code is `200`. When a successful query is executed, the JSON object that is returned contains a property called `data` whose value is an object representing the returned data. Here is an example:
```json
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"items": [
{
"accountNo": 9001,
"name": "Special account",
}
]
}
}
}
}
```
When an error occurs during the execution of the request, the return JSON object contains both the `data` property, as well as a property called `errors`, which is an array of objects containing information about the error(s) that occurred. The following is an example:
```json
{
"errors": [
{
"message": "Error: Could not get authorize user using VismaNetCompanyId: 1234567. Description: External integration error. Status: 18."
}
],
"data": {
"useCompany": {
"generalLedgerAccount": null
}
}
}
```
If the GraphQL query has a syntax error, the returned information contains not just a message but also detains about the location of the error within the query. This is exemplified below:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no : $cid) {
generalLedgerAccount {
totalRows
}
}
}
```
```graphql { title = "Result" }
{
"errors": [
{
"message": "GraphQL.Validation.Errors.FieldsOnCorrectTypeError: Cannot query field 'totalRows' on type 'Query_UseCompany_GeneralLedgerAccount_Connection'. Did you mean 'totalCount'?",
"locations": [
{
"line": 4,
"column": 7
}
],
"extensions": {
"code": "FIELDS_ON_CORRECT_TYPE",
"codes": [
"FIELDS_ON_CORRECT_TYPE"
],
"number": "5.3.1"
}
}
]
}
```
A GraphQL query may contain multiple requests (such as reading from different tables). It is possible that some may be successful, while other will fail. The result will contain data that was fetched successfully but also information about the errors that occurred for the other requests. Here is an example:
```graphql { title = "Query" }
query read($cid : Int!)
{
useCompany(no : $cid) {
generalLedgerAccount(first: 2) {
totalCount
items {
accountNo
name
}
}
generalLedgerBalance(last: 10) {
totalCount
items {
accountNo
sumDebitObDomestic
sumCreditObDomestic
}
}
}
}
```
```graphql { title = "Result" }
{
"errors": [
{
"message": "The cursor 'before' must have a value.",
"path": [
"useCompany",
"generalLedgerBalance"
]
}
],
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 340,
"items": [
{
"accountNo": 1000,
"name": "Forskning og utvikling"
},
{
"accountNo": 1020,
"name": "Konsesjoner"
}
]
},
"generalLedgerBalance": null
}
}
}
```
On the other hand, an operation may be successful (such as inserting a new row) although sub-operations (such as assigning a value to a column) may fail. GraphQL returns the result as well as all the errors messages from executing the request.
```graphql { title = "Query" }
mutation create_batch($cid : Int!)
{
useCompany(no: $cid)
{
batch_create(values:
[
{
valueDate: 20211122
voucherSeriesNo: 3
orgUnit1 : 0
orgUnit2 : 0
orgUnit3 : 0
orgUnit4 : 0
orgUnit5 : 0
period :11
year :2021
}
])
{
affectedRows
items
{
batchNo
valueDate
voucherSeriesNo
}
}
}
}
```
```graphql { title = "Result" }
{
"errors": [
{
"message": "Error: Illegal value date 11/22/2021. Check suspension date and the accounting periods and VAT periods tables..",
"path": [
"useCompany",
"batch_create",
"values/0"
],
"extensions": {
"data": {
"status": 0
}
}
},
{
"message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit3.",
"path": [
"useCompany",
"batch_create",
"values/0"
],
"extensions": {
"data": {
"status": 3,
"status_name" : "NotReadAccessToDestinationColumn"
}
}
},
{
"message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit4.",
"path": [
"useCompany",
"batch_create",
"values/0"
],
"extensions": {
"data": {
"status": 3,
"status_name" : "NotReadAccessToDestinationColumn"
}
}
},
{
"message": "Error: Org unit class not named. Description: Not read access to destination column. Column: OrgUnit5.",
"path": [
"useCompany",
"batch_create",
"values/0"
],
"extensions": {
"data": {
"status": 3,
"status_name" : "NotReadAccessToDestinationColumn"
}
}
}
],
"data": {
"useCompany": {
"batch_create": {
"affectedRows": 1,
"items": [
{
"batchNo": 26,
"valueDate": 20211122,
"voucherSeriesNo": 3
}
]
}
}
}
}
```
## Understanding error information
When an error occurs during the execution of the query, you may see the following information for each returned error:
- `message`: always present, contains a description of the error
- `path`: contains the path in the query (schema) of the field that produced the error
- `extensions`: additional information about the error. An example is `data:status` that contains an internal error code that could be useful for troubleshouting a failed execution. In addition, `data:status_name` provides a symbolic name of the status code, such as `NotReadAccessToDestinationColumn`.
Another example of an error message from attempting to create an order with a duplicate ID is shown below. You can see that `status` is 3 but `status_name` is set to `PrimaryKeyAssignmentFailed` to give you a better understanding of what the status code 3 means in this context.
```json
{
"errors": [
{
"message": "A record with the same primary key already exists.",
"path": [
"useCompany",
"order_create",
"values/0"
],
"extensions": {
"data": {
"status": 3,
"status_name": "PrimaryKeyAssignmentFailed"
}
}
}
],
"data": {
"useCompany": {
"order_create": {
"items": null
}
}
}
}
```
However, it is important to note that these status codes (and their names) are not unique. A request is composed of multiple operations, such as selecting a table, assigning a value to a field, etc. There are various status codes for each such contextual operation.
Therefore, an operation may return status code 3 that means `NotReadAccessToDestinationColumn`, or status code 3 that means `NotInsertAccessToTable`.
That is why the `status_name` field is useful to help you better understand the problem.
> [!TIP]
>
> For more information about the GraphQL engine errors (such as schema errors, input errors and processing errors) see [GraphQL.NET Error Handling](https://graphql-dotnet.github.io/docs/getting-started/errors/).
## Tracing query execution
Each GraphQL query that executes is assigned a unique identifier. This is used to trace the execution of the query and can be used for identifying problems with the execution. If you need to contact the Business NXT support for help to investigate a problem, you need to provide this unique identifier.
You can find this unique ID in the response of a GraphQL request. Although this was skipped in the examples shown in this tutorial (for simplicity), each response has a field called `extensions` that contains an object named `vbnxt-trace-id`.
```graphql { title = "Query" }
query read($cid : Int, $pagesize : Int) {
useCompany(no: $cid) {
generalLedgerAccount(first: $pagesize) {
totalCount
items {
accountNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 340,
"items": [
{
"accountNo": 1000,
"name": "Forskning og utvikling"
},
{
"accountNo": 1020,
"name": "Konsesjoner"
},
...
]
}
}
},
"extensions": {
"vbnxt-trace-id": "00000000000000000196b4ea383242fa"
}
}
```
Use the value of the `vbnxt-trace-id` when contacting the Business NXT support.
> [!TIP]
>
> The same trace identifier can be retrieved from the response headers. GraphQL responses have a custom `x-vbc-traceid` header containing the value of the trace identifier.
Unoptomized queries
/businessnxtapi/features/unoptimized
page
Learn about the limitations and handling of optimized queries for joined tables, including performance trade-offs and the usage of the unoptimized Boolean argument for accurate pagination and counting.
2025-04-15T09:48:42+02:00
# Unoptomized queries
Learn about the limitations and handling of optimized queries for joined tables, including performance trade-offs and the usage of the unoptimized Boolean argument for accurate pagination and counting.
Because of the way fetching data from the backend is optimized, several features do not work for joined tables (the fields that start with the `joindown_` or `joinup_` prefix):
- pagination (requesting data in pages and diplaying paging information - the `pageInfo` field)
- counting the total number of rows that match the filter (the `totalCount` field)
In order to have these features working properly, you must explicitly request that the execution of the query is not optimized by the system. This is done with a Boolean field argument called `unoptimized`. The way this works is described below. First, let's understand the problem.
## Query optimization
When you request data from joined tables we are faced with a problem called the N+1 problem. Let's say you want to fetch the last 10 orders of a customer, but along with the order heads also the order lines info. Typically, this means we will do one request to fetch the order heads first, and then, for each order, we perform one request to fetch the lines. For 10 orders that is 10 more request for the lines, amounting to a total of 11 requests. For N orders, that amounts to N+1 requests. This incurs a performace loss and this is aggravated if more tables are joined together. For instance, fetching the orders, and for each order the order lines, and for each order line the order notes, just to give an example.
To avoid the performance penalty, our system is optimizing the queries. As a result, we are performing only 2 requests instead of N+1. For the orders and orders line example, we perform a first request to fetch the heads and then a second request to fetch the lines.
The result is a potential 10x or more speed-up for executing a query. The downside is the features mentioned above no longer work.
## The problem with the optimization
The understand the problem with optimization let us discuss the following scenario.
Consider a query that fetches the first 10 orders and for each order the first 3 order lines. That means that for each order, the query should return a maximum of 3 order lines, therefore, potentially, a total of 30 order lines in total. However, some orders may have less than 3 order lines others may have more. The current implementation takes the first 30 order lines (that match the given filter, if any) regardless to which order they belong. Let's explain this with an example:
| Order no | No. of order lines | No. of expected returned lines | No. of actual returned lines |
| -------- | ----------- | ----------------------- | --------------------- |
| 1 | 2 lines | 2 lines | 2 lines |
| 2 | 5 lines | 3 lines | 5 lines |
| 3 | 2 line | 2 lines | 2 lines |
| 4 | 1 lines | 1 lines | 1 lines |
| 5 | 7 lines | 3 lines | 7 lines |
| 6 | 3 lines | 3 lines | 3 lines |
| 7 | 5 lines | 3 lines | 5 lines |
| 8 | 8 lines | 3 lines | 5 lines |
| 9 | 5 lines | 3 lines | 0 lines |
| 10 | 2 lines | 2 lines | 0 lines |
The third column in this table shows the expected result when you ask for the first 3 order lines for each order. What is returned instead is the data of the fourth column. This is shown with the following query:
```graphql { title = "Query" }
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order(first: 3)
{
items
{
lineNo
}
}
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 348,
"items": [
{
"orderNo": 1,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 2,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 3,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 4,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
}
]
}
},
{
"orderNo": 5,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
},
{
"lineNo": 6
},
{
"lineNo": 7
}
]
}
},
{
"orderNo": 6,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 7,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 8,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 9,
"joindown_OrderLine_via_Order": {
"items": null
}
},
{
"orderNo": 10,
"joindown_OrderLine_via_Order": {
"items": null
}
}
]
}
}
}
}
```
The reason for this is that we fetch a total of 10\*3 order lines from the order lines table, in a single request. This means the result is not what was actually requested from with the query.
Notice this is not a problem when no pagination is requested. The following example, that fetches some orders and all their order lines works without any problem.
```graphql { title = "Query" }
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order
{
items
{
lineNo
}
}
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 348,
"items": [
{
"orderNo": 1,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 2,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 3,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 4,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
}
]
}
},
{
"orderNo": 5,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
},
{
"lineNo": 6
},
{
"lineNo": 7
}
]
}
},
{
"orderNo": 6,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 7,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 8,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
},
{
"lineNo": 6
},
{
"lineNo": 7
},
{
"lineNo": 8
}
]
}
},
{
"orderNo": 9,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
},
{
"lineNo": 4
},
{
"lineNo": 5
}
]
}
},
{
"orderNo": 10,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
}
]
}
}
}
}
```
However, fetching the total count of order lines per each order and displaying paging information for each chunk of order lines does not work in this case either.
## Unoptimized queries
To solve the problem of the optimized queries for joined tables, you can explicitly request to run the query without optimizations. This means there will be N+1 requests to the backend and the execution time will increase significantly. However, counting records from the joined table and fetching data in pages works as expected.
The unoptimized execution is requested with a Boolean argument for the field called `unoptimized` that must be set to `true`. This is shown in the following example:
```graphql { title = "Query" }
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order(first: 3,
unoptimized: true)
{
items
{
lineNo
}
}
}
}
}
}
```
```graphql { title = "Result" }
{
"data": {
"useCompany": {
"order": {
"totalCount": 348,
"items": [
{
"orderNo": 1,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 2,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 3,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
},
{
"orderNo": 4,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
}
]
}
},
{
"orderNo": 5,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 6,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 7,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 8,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 9,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
},
{
"lineNo": 3
}
]
}
},
{
"orderNo": 10,
"joindown_OrderLine_via_Order": {
"items": [
{
"lineNo": 1
},
{
"lineNo": 2
}
]
}
}
]
}
}
}
}
```
The `unoptimized` flag is an argument to a connection (see [Queries](../schema/queries/query.md)). However, a connection may contain other inner connections. These are the joindown tables discussed here and seen in the previous examples.
When you are requesting the fetching of data in an unoptimized manner, this applies not only to the connection on which the argument was specified, but also on all its descendants.
This behavior can be overridden by using the `unoptimized` argument again on a descendant connection. Here are several examples:
- the order lines are fetched in an optimized manner but not the order line notes
```graphql
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order(first: 3)
{
items
{
lineNo
joindown_OrderLineNote_via_OrderLine(unoptimized : true)
{
items
{
note
}
}
}
}
}
}
}
}
```
- both the order lines and the order line notes are fetched unoptimized
```graphql
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order(first: 3, unoptimized : true)
{
items
{
lineNo
joindown_OrderLineNote_via_OrderLine
{
items
{
note
}
}
}
}
}
}
}
}
```
- the order lines are fetched unoptimized but the fetching of order line notes is optimized
```graphql
query ($cid :Int!, $pagesize : Int)
{
useCompany(no: $cid)
{
order(first: $pagesize)
{
totalCount
items
{
orderNo
joindown_OrderLine_via_Order(first: 3, unoptimized : true)
{
items
{
lineNo
joindown_OrderLineNote_via_OrderLine(unoptimized : false)
{
items
{
note
}
}
}
}
}
}
}
}
```
Using the `unoptimized` flag on a top-level table (such as the orders table in these examples) has no effect on fetching data from that table. However, it will affect the manner of fetching data from all its joined tables (the fields prefixed with either `joindown_` or `joinup_`).
## Asynchronous execution
Executing the queries with joined down tables fast and having pagination or counting the total items in the joined down table are mutually exclusive. However, if you do need these features but do not want to wait for the execution of the query you can execute the query asynchronously.
To learn more about this, see [Async queries](../schema/async.md).
Batch requests
/businessnxtapi/features/batches
page
Batch multiple GraphQL queries or mutations in a single API request, with results returned after complete execution.
2025-04-15T09:48:42+02:00
# Batch requests
Batch multiple GraphQL queries or mutations in a single API request, with results returned after complete execution.
A GraphQL request is basically a JSON object that has the following form:
```json
{
"query" : "..."
"variables" : {}
"operationname" : "..."
}
```
The `OperationName` property is optional, but if present, it must match the operation name from the query. To exemplify, let's consider the following request:
```graphql { title = "Query" }
query read_accounts($cid : Int,
$pagesize : Int){
useCompany(no: $cid) {
generalLedgerAccount(first: $pagesize) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
items {
accountNo
name
}
}
}
}
```
```graphql { title = "Result" }
{
"cid" : 987654321,
"pagesize" : 10
}
```
This query is formatted as the following JSON content when dispatched to the GraphQL API:
```json
{
"query" : "query read_accounts($cid : Int,\n $pagesize : Int){\n useCompany(no: $cid) {\n generalLedgerAccount(first: $pagesize) {\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items {\n accountNo\n name\n }\n }\n }\n}",
"variables": {"cid": 987654321, "pagesize" : 10},
}
```
> [!NOTE]
>
> Notice that JSON does not support multiple line strings. Therefore, new lines must be escaped, and the entire query provided as a single line string.
The result you get back is also a JSON object, such as the following:
```json
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 412,
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MA==",
"endCursor": "MTA="
},
"items": [
{
"accountNo": 1200,
"name": "Plant and machinery"
},
{
"accountNo": 1209,
"name": "Depreciation plant and machinery"
},
...
]
}
}
},
"extensions": {
"vbnxt-trace-id": "a2f3f1ad045d7bd2f62f833e136ff0b0"
}
}
```
It is possible that a single request contains multiple parts. For instance, you can query orders, products, and customers in a single request. You can also create an order and its order lines in a single mutation.
However, it's not possible to mix queries and mutations inside the same request.
VBNXT GraphQL allows you to batch multiple queries inside a single API requests. That is possible by sending an array of JSON objects, as follows:
```json
[
{
"query" : "..."
"variables" : {}
"operationname" : "..."
},
{
"query" : "..."
"variables" : {}
"operationname" : "..."
}
]
```
The result, is also an array of objects, one for each requested operation.
```json
[
,
,
...
]
```
An example with two requests (a query and a mutation) batched together is shown below:
```json
[
{
"query" : "query read($cid : Int, $pagesize : Int){\n useCompany(no: $cid) {\n generalLedgerAccount(first: $pagesize) {\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n items {\n accountNo\n name\n }\n }\n }\n}",
"variables": {"cid": 987654321, "pagesize" : 10},
"operationname" : "read"
},
{
"query" : "mutation update_gla($cid : Int!, $no : Int!, $name : String!)\n{\n useCompany(no: $cid) \n {\n generalLedgerAccount_update(\n filter : {accountNo : {_eq: $no}},\n value : {shortName : $name})\n {\n affectedRows\n items {\n accountNo\n name\n shortName\n }\n }\n }\n}",
"variables": {"cid": 987654321, "no" : 9999, "name": "test"},
"operationname" : "update_gla"
}
]
```
```json
[
{
"data": {
"useCompany": {
"generalLedgerAccount": {
"totalCount": 117,
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "MA==",
"endCursor": "MTA="
},
"items": [
{
"accountNo": 1200,
"name": "Plant and machinery"
},
{
"accountNo": 1209,
"name": "Depreciation plant and machinery"
},
{
"accountNo": 1210,
"name": "Buildings"
},
{
"accountNo": 1211,
"name": "Land"
},
{
"accountNo": 1219,
"name": "Depreciation, Buildings"
},
{
"accountNo": 1220,
"name": "Office Equipment"
},
{
"accountNo": 1229,
"name": "Depreciation, Office Equipment"
},
{
"accountNo": 1240,
"name": "Motor vehicles"
},
{
"accountNo": 1249,
"name": "Depreciation motor vehicles"
},
{
"accountNo": 1300,
"name": "Goodwill"
}
]
}
}
}
},
{
"data": {
"useCompany": {
"generalLedgerAccount_update": {
"affectedRows": 1,
"items": [
{
"accountNo": 9999,
"name": "SUSPENSE ACCOUNT",
"shortName": "test"
}
]
}
}
}
}
]
```
It is possible to batch both queries and mutations together in any order. They are executed individually, one by one, in the order they where provided.
When batching multiple request, the results will only be returned after all the requests have been executed. A large number of requests could make the overall operation exceed the HTTP timeout interval.
In this case, the operation will timeout and the result will be lost.
> [!NOTE]
>
> There is currently a limit of the number of requests that can be batched together. This limit is set to 32 requests.
>
> This limit may be subject to future changes.
> [!TIP]
>
> Use batching requests judiciously, making sure their overall execution time will not trigger an operation timeout!