Incremental loads and deletions
Reading a whole table on every refresh gets slow as Business Central grows. BI Endpoints gives you two things to load incrementally instead:
systemModifiedAton every endpoint — the records created or changed since your last load.deletedEntries— the records deleted since your last load.
Together they keep a copy in a warehouse or lakehouse exactly in step with Business Central.
Changed records: systemModifiedAt
Every endpoint includes systemModifiedAt, when the record was created or
last modified. Filter on it to read only what changed since your last
extraction:
GET .../companies({companyId})/glEntries?$filter=systemModifiedAt ge 2026-01-01T00:00:00.000Z
A typical loop:
- Keep the time of your last successful extraction in a control table.
- Before you start, note the current time.
- Read each endpoint with
$filter=systemModifiedAt ge <last extraction>. - Upsert the rows by the endpoint's business key — for example
entryNoforglEntries, ordocumentNoandlineNoforsalesInvoiceLines. - Read and apply the deleted entries for the same period (below).
- When everything succeeded, store the time from step 2 as the new last extraction.
Using ge and the time the load started means a record changed while the
load ran is read again on the next run rather than missed; upserting by
business key makes the overlap harmless.
Deleted entries
Deleted records are tracked by default for every BI endpoint and served from
a separate endpoint, deletedEntries (Premium):
GET .../companies({companyId})/deletedEntries?$filter=entitySetName eq 'glEntries' and systemModifiedAt ge 2026-01-01T00:00:00.000Z
Each row is one deleted record:
| Field | Meaning |
|---|---|
entryNo | Sequence number of the log entry. |
entitySetName | The endpoint the record belonged to, for example glEntries. |
tableId, tableName | The Business Central source table. |
key1Name … key6Name, key1Value … key6Value | The record's business key, one field per part, in the order of the endpoint's business key. |
primaryKeyText, primaryKeyJson | The key as text and as JSON. |
keyTruncated | true when a key value was too long to store in full. |
deletedSystemId | The Business Central SystemId of the deleted record. |
deletedAt, deletedBy | When and by whom it was deleted. |
systemModifiedAt | When the log entry was written. Filter on it as you do on the data endpoints. |
Business key values are rendered in the API output language, so they match the values the data endpoint returned.
Delete the matching keys from your copy. You don't need to build the URL
yourself: each endpoint's Deleted Records URL — on its page in Business
Central, or deletedRecordsUrl in biEndpoints —
already filters on the endpoint and selects exactly the key columns it uses:
.../deletedEntries?$filter=entitySetName eq 'salesInvoiceLines'&$select=entryNo,tableName,key1Name,key1Value,key2Name,key2Value,primaryKeyText,deletedAt,deletedBy,systemModifiedAt
Add and systemModifiedAt ge <last extraction> to its filter.
Turning delete tracking off
Tracking can be switched off for every endpoint, or per endpoint, and old log entries are removed by a retention policy. See BI Endpoints Setup. Make sure your loads run more often than the retention period, or a deletion can be purged before you read it.
With Cloud Connector
Cloud Connector sets this up for
you: adding a BI Endpoints entity set as a dataset creates a full read, an
incremental read on systemModifiedAt, and a delete read of
deletedEntries, and carries deletions through to SQL Server or Fabric.