$batch, attachments), or the dedicated Business Central node for the guided path of creating or updating a single entity, including automatic write-back to a Reconciled record. Both authenticate through a Business Central connector, which supplies the app-only bearer token and the API base URL.
This guide covers the HTTP action patterns in depth: deep insert, $batch, and the source-document attachment recipe. For the guided single-entity path, see the Business Central node page instead. Within the HTTP action path there are two write patterns for creating a record: deep insert and $batch. Pick the simpler one when it applies.
To read a record out of Business Central mid-run rather than write one, set the Business Central node’s operation to Look up. See Read a record with the lookup operation at the end of this guide, including the worked invoice to purchase order flow.
1. Connect
- Register an app in Microsoft Entra ID with the Application permission
Dynamics 365 Business Central > API.ReadWrite.All(admin consent), create a client secret, and register the app in Business Central under Microsoft Entra Applications with a permission set. See Connectors for the step-by-step. To confirm purchase orders from vendor confirmations, also install the Ingestly Connector extension and assign its Ingestly Purchase Amendment permission set (see Connectors). - Open Settings → Connectors and create a connector of type Business Central.
- Enter the Tenant ID, Client ID and Client Secret, the environment name (
Productionor your sandbox name), the Company ID (the default company GUID) and, optionally, the secret’s expiry date. - Click Connect. Ingestly requests a token immediately; no sign-in is shown. The connector now exposes a base URL like
https://api.businesscentral.dynamics.com/v2.0/{tenantId}and injects the bearer token on every request.
2. Deep insert (recommended for one parent + its children)
A deep insert is a singlePOST to a parent entity whose body contains its OData navigation children inline. Business Central creates the parent and all children atomically.
Use it when you want to create one parent (sales quote, sales order, sales invoice, purchase invoice, …) along with its lines in one call.
HTTP action configuration
salesQuotes→salesQuoteLinessalesOrders→salesOrderLinessalesInvoices→salesInvoiceLinespurchaseInvoices→purchaseInvoiceLines
3. When deep insert isn’t enough: use $batch
Deep insert only handles “one parent and its immediate children, all created together.” Use OData $batch when you need any of:
- Mix methods in one round-trip transaction (
POST+PATCH+DELETE). - Cross-entity transactions across unrelated parents (e.g. create a customer and a vendor in one transaction).
- Bulk creation across many parents in a single HTTP call (50 quotes in one request rather than 50 requests).
- Coordinate separate endpoints in one transaction, such as creating a quote and an order together.
Business Central’s
$batch does not implement OData v4 $<contentId> URL references between operations, so each operation must specify its full target path. Ingestly rejects any operation whose url starts with $ before the request is sent, with guidance to move the dependent operation into a follow-up HTTP action step. For “create parent A, then attach child to A’s new id,” use deep insert (children nested in the parent body), or chain a second HTTP action step after the batch step. A $batch response is a responses array with one entry per operation, so the first operation’s new id is {{NodeName.payload.responses[0].body.id}}; a JSON deep insert returns the record itself, so its id is {{NodeName.payload.id}}. The attachment recipe is this pattern worked through.$batch request with Isolation: snapshot on the server.
HTTP action configuration
Body shape
Operation shape
The body editor validates the shape live: missing fields, unknown keys, and wrong methods are flagged inline as you type. An operation that sets both
body and binaryBody, or a binaryBody that is not valid Base64, is rejected with a per-operation error.
To attach the source document to a record you create in the batch, you cannot upload it in the same batch: Business Central does not resolve
$<contentId> references, so the upload cannot target the just-created record. Use a follow-up HTTP action step instead. See Attach the source document to a record.Templating
The whole body is a string passed through Ingestly’s template engine, so you can build the operations array dynamically from upstream node output, e.g.{{aggregator.payload.operations}}.
4. Notes & limits
- One transaction per request. Ingestly sends
Isolation: snapshot, so the operations run in one Business Central transaction when the invoked APIs do not force their own commit. - Response is JSON. Business Central replies with a
responsesarray containing one inner response per operation. - Connector base URL is required. The Business Central connector enforces a list of allowed hosts; absolute URLs are rejected for safety.
- Parent plus children. Put child collections like
salesQuoteLinesinside the parentsalesQuotesbody. Use extra batch operations for separate endpoints likesalesOrders. - Write back changes. To push changes into Business Central, use the Business Central node in its Update operation: it compiles a vendor-neutral operations document into a transactional
$batchrequest automatically, so you never write the entity-set paths or key syntax used in this guide’s HTTP action examples. Pick Reconcile as the record source for the guided, verified path (an upstream Reconcile result and its safety gate), or Direct when the record id is already known some other way, for example extracted from the document, fetched by an earlier HTTP action step, or read from a Store node.
5. Attach the source document to a record
Business Central stores a file attachment as adocumentAttachments record plus a separate binary upload of the file content. That is two calls, and the second needs the id the first returns. Business Central does not resolve $<contentId> references inside a $batch (see the note in section 3), so the two operations cannot ride in one batch. Use two HTTP action steps in sequence instead: the second step is connected downstream of the first and references its response by node name, the same way any two action nodes chain in a workflow. The dedicated Business Central node cannot take either step: it only addresses an entity set under the company (companies({id})/{entity}), and the upload targets a navigation path on one attachment record.
This recipe attaches the run’s source PDF to a purchase invoice created by an upstream HTTP action deep insert step named invoice (see Deep insert). It was verified against a live Business Central sandbox on 2026-07-24.
HTTP action A: create the attachment record (JSON POST)
parentId reads the created invoice’s system id from the upstream invoice step’s response. parentType is the Business Central document type, exactly Purchase Invoice for a purchase invoice.
HTTP action B: upload the file bytes (Binary PATCH)
Connect this step downstream of HTTP action A (named attach here).
The binary body sends the raw file bytes with
application/octet-stream. The If-Match: * header is required: Business Central rejects a content stream PATCH without it (see the error playbook).
6. Business Central error playbook
Business Central returns terse OData errors. These are the ones you are most likely to hit when writing records and attachments, with the fix for each.7. Read a record with the lookup operation
Everything above writes. To read a record back out of Business Central during a run, add a Business Central node and set its Operation to Look up: pick the connector, pick the entity, and write one or more rules that say which record you want. Ingestly compiles the rules into the OData query and hands you the matched record as the step’spayload, so you never write a $filter string, a company path segment, or a URL.
That is the guided path, and it is the one to reach for first.
When to use an HTTP action GET instead
The lookup operation deliberately expresses one shape of read: filter an entity set, optionally narrow the fields, take the first match or a page of matches. Use an HTTP actionGET for a read it does not cover:
- More than 100 results. The lookup returns at most 100 records and has no paging.
- Ordering. The lookup emits no
$orderby, so “the most recent one” is not something its rules can ask for. - Counts and aggregates. There is no
$countand no aggregation. - Filter shapes the operators do not cover, such as a negated
contains, or an OData function the rule editor does not offer. - Anything that is not an entity-set read, such as a metadata, action or report endpoint.
Worked flow: match an invoice to its purchase order
The document is a vendor invoice that names a purchase order. The workflow has to find that purchase order in Business Central, check the two agree, and write the result back only when they do.- Extract pulls the invoice fields off the document, including the purchase order number as
po_number. - Business Central node named
FindPO, OperationLook up, entitypurchaseOrders, ResultFirst match, one rule: fieldnumber, operator Equals, value{{extract.payload.po_number}}. AddpurchaseOrderLinesto Expand if the comparison needs the lines. - If, on
{{FindPO.metadata.businessCentral.found}}equalstrue. This step is what makes the flow safe: a missing purchase order is a successful lookup with an empty payload, not a failed step, so nothing else will tell you it was not found.- The False port is the “no such purchase order” path. Send it to a Review so a person can resolve it, or to a notification, or leave it unconnected so the run stops there and still completes.
- Reconcile, on the True port, with the extract as one source and
FindPOas the other. Add field rules for the values that have to agree (the total, the vendor, the line quantities) and set the ones that must not slip to Block. - Review, on the reconcile mismatch path, so a person approves or corrects a blocking difference before anything is written.
- Business Central node in Update operation, with Reconcile as its record source, writes the approved result back. Open Advanced and set Record id path to
{{id}}. The default,{{value[0].id}}, is the path into a raw OData collection response; a First match lookup hands the reconcile the bare record, so the record id sits atid. With the default left in place the step fails with “Write-back could not resolve the target record id”.
Ingestly does not know the shape of a Business Central record, so the Reconcile field-rule editor cannot suggest or check field names on the
FindPO side. Type the connector’s own field name (for example totalAmountIncludingTax) and it resolves at run time. If you narrow the lookup with Select, list the fields the reconcile compares and keep id in the list: Business Central returns exactly the fields you select, and the write-back reads the record id from the same record. (A QuickBooks lookup feeding a QuickBooks update needs Id and SyncToken the same way.)