Quantcast
Channel: Kauffmann @ Dynamics 365 Business Central
Viewing all articles
Browse latest Browse all 118

Batch calls with Business Central APIs (1) – Basic operation

$
0
0

A while ago I wrote about deep inserts with Business Central APIs. And I promised to write about batch calls too, so it’s about time to live up to that. 😀 Actually, I did some online sessions about it, like this one for DynamicsCon and also for Directions 2020. But I think it is still worthy to write it down. And because there is a lot to share about this topic, it’s my plan to write a series of posts. Today, we start with the basics of batch calls.

Background

When calling Business Central APIs you do one operation at a time. For example, you can only insert or modify one customer, or create one sales invoice. With deep inserts, it is possible to create header and lines together, and then you can create multiple lines. But that’s only possible on the line records, you still create one header at a time. With a batch request, it is possible to combine multiple operations in one request. The batch request is submitted as a single HTTP POST request to the $batch endpoint and the body contains a set of operations. From an optimization point of view, batching minimizes the number of requests from an API consumer because you combine multiple requests into one.

Batch URL

The $batch endpoint is available on all API endpoints. For the Business Central SaaS environment you can use this URL:

https://{{baseurl}}/api/v2.0/$batch

Everything I describe here also works for custom APIs. The URL will then look like:

https://{{baseurl}}/api/[publisher]/[group]/[version]/$batch

The parameter {{baseurl}} can be replaced with the standard endpoint URLs for Business Central as explained here: https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v2.0/endpoints-apis-for-dynamics.

Request headers

Because the request body will be JSON the Content-Type header of the request must be set to appication/json. If the response should be JSON as well (and you want that!), then the Accept header must also be set to application/json. If you leave the Accept header out, then the response will be multipart/mixed. Here is the basic structure of the request, without the body. I leave out the Authorization header, but you need to add that obviously. 🙂

POST {{baseurl}}/api/v2.0/$batch
Content-Type: application/json
Accept: application/json

Request body

There are two body types supported when doing batch requests: multipart/mixed and application/json. Because multipart/mixed is more complex to use and read while the JSON body is much more readable and works fine with Business Central API, I will only discuss application/json in this blog post. The request body is a JSON with this basic format:

{
	"requests": []
}

As you can see, that’s quite a simple JSON payload, isn’t it?

The requests array must contain one ore more operations, and each of them must contains an id, the method and a URL and optionally also headers and a body. Here is an example of an operation to insert a single journal line.

{
	"method": "POST",
	"id": "r1",
	"url": "companies({{companyId}})/journals({{journalId}})/journalLines",
	"headers": {
		"Content-Type": "application/json"
	},
	"body": {
        "accountId": "{{accountId}}",
        "postingDate": "2020-10-20",
		"documentNumber": "SALARY2020-10",
		"amount": -3250,
		"description": "Salary to Bob"
	}
}

Each operation has a number of properties:

id

mandatory

Unique identification of this operation.

method

mandatory

One of the standard HTTP methods GET, POST, PATCH, PUT or DELETE. This value is case insensitive.

url

mandatory

Path to the API. This can be a relative path or an absolute path. 
relative path replaces the $batch part of the batch request url.
An absolute path replaces the complete url of the batch request.
The url may contain parameters like $select, $filter, etc.

headers

optional

List of headers for this operation.
Format: "header-name": "value"

body

optional

Mandatory if the request has method POST, PATCH or PUT.
The content of the body is the same as for a single request. For Business Central APIs this is usually a JSON payload. The headers must contain a Content-Type header that indicates the type of data in the body.

Let’s take the operation above, to insert a single journal line and compose a batch request that inserts multiple journal lines in one go. The request body looks like:

{
	"requests": [
		{
			"method": "POST",
			"id": "r1",
			"url": "companies({{companyId}})/journals({{journalId}})/journalLines",
			"headers": {
				"Content-Type": "application/json"
			},
			"body": {
			    "accountId": "{{accountId}}",
			    "postingDate": "2020-10-20",
			    "documentNumber": "SALARY2020-12",
			    "amount": -3250,
			    "description": "Salary to Bob"
			}
		},
		{
			"method": "POST",
			"id": "r2",
			"url": "companies({{companyId}})/journals({{journalId}})/journalLines",
			"headers": {
				"Content-Type": "application/json"
			},
			"body": {
			    "accountId": "{{accountId}}",
			    "postingDate": "2020-10-20",
			    "documentNumber": "SALARY2020-12",
			    "amount": -3500,
			    "description": "Salary to John"
	        }
		},
        {
			"method": "POST",
			"id": "r3",
			"url": "companies({{companyId}})/journals({{journalId}})/journalLines",
			"headers": {
				"Content-Type": "application/json"
			},
			"body": {
			    "accountId": "{{accountId2}}",
			    "postingDate": "2020-10-20",
			    "documentNumber": "SALARY2020-12",
			    "amount": 6750,
			    "description": "Salaries December 2020"
	        }
		}	
	]
}

As you can imagine, this can be done for inserting multiple customers, items, invoices, etc. The body can even be combined with deep inserts!

The response

Just like the request body is a combination of multiple operations, the response body is a combination of the multiple results. Let’s take a look at the response body for the batch request example:

{
    "responses": [
        {
            "id": "r1",
            "status": 201,
            "headers": {
                "location": "https://bcsandbox.docker.local:7048/bc/api/v2.0/companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines(6a9fec9f-6a40-eb11-a853-d0e7bcc597da)",
                "content-type": "application/json; odata.metadata=minimal",
                "odata-version": "4.0"
            },
            "body": {
                "@odata.context": "https://bcsandbox.docker.local:7048/bc/api/v2.0/$metadata#companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines/$entity",
                "@odata.etag": "W/\"JzQ0O0hOcTdrU25sTDNHNnRjTnBqMHNNMm94ZDUwK1JFK0txSmtkc0VQemN6Nmc9MTswMDsn\"",
                "id": "6a9fec9f-6a40-eb11-a853-d0e7bcc597da",
                "journalId": "f91409ba-1d3d-eb11-bb72-000d3a2b9218",
                "journalDisplayName": "DEFAULT",
                "lineNumber": 10000,
                "accountType": "G_x002F_L_x0020_Account",
                "accountId": "ae4110b4-1d3d-eb11-bb72-000d3a2b9218",
                "accountNumber": "60700",
                "postingDate": "2020-10-20",
                "documentNumber": "SALARY2020-12",
                "externalDocumentNumber": "",
                "amount": -3250.00,
                "description": "Salary to Bob",
                "comment": "",
                "taxCode": "NONTAXABLE",
                "balanceAccountType": "G_x002F_L_x0020_Account",
                "balancingAccountId": "00000000-0000-0000-0000-000000000000",
                "balancingAccountNumber": "",
                "lastModifiedDateTime": "2020-12-17T13:20:26.873Z"
            }
        },
        {
            "id": "r2",
            "status": 201,
            "headers": {
                "location": "https://bcsandbox.docker.local:7048/bc/api/v2.0/companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines(6b9fec9f-6a40-eb11-a853-d0e7bcc597da)",
                "content-type": "application/json; odata.metadata=minimal",
                "odata-version": "4.0"
            },
            "body": {
                "@odata.context": "https://bcsandbox.docker.local:7048/bc/api/v2.0/$metadata#companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines/$entity",
                "@odata.etag": "W/\"JzQ0O1cwbTBRYms5SVVjVEMzbzhCckhyc25YMzJ3N2paRGJWUXVyNDlTSGwvcU09MTswMDsn\"",
                "id": "6b9fec9f-6a40-eb11-a853-d0e7bcc597da",
                "journalId": "f91409ba-1d3d-eb11-bb72-000d3a2b9218",
                "journalDisplayName": "DEFAULT",
                "lineNumber": 20000,
                "accountType": "G_x002F_L_x0020_Account",
                "accountId": "ae4110b4-1d3d-eb11-bb72-000d3a2b9218",
                "accountNumber": "60700",
                "postingDate": "2020-10-20",
                "documentNumber": "SALARY2020-12",
                "externalDocumentNumber": "",
                "amount": -3500.00,
                "description": "Salary to John",
                "comment": "",
                "taxCode": "NONTAXABLE",
                "balanceAccountType": "G_x002F_L_x0020_Account",
                "balancingAccountId": "00000000-0000-0000-0000-000000000000",
                "balancingAccountNumber": "",
                "lastModifiedDateTime": "2020-12-17T13:20:26.927Z"
            }
        },
        {
            "id": "r3",
            "status": 201,
            "headers": {
                "location": "https://bcsandbox.docker.local:7048/bc/api/v2.0/companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines(6c9fec9f-6a40-eb11-a853-d0e7bcc597da)",
                "content-type": "application/json; odata.metadata=minimal",
                "odata-version": "4.0"
            },
            "body": {
                "@odata.context": "https://bcsandbox.docker.local:7048/bc/api/v2.0/$metadata#companies(9f161476-1d3d-eb11-bb72-000d3a2b9218)/journals(f91409ba-1d3d-eb11-bb72-000d3a2b9218)/journalLines/$entity",
                "@odata.etag": "W/\"JzQ0O25SN2NHT2s3QklhTVVNUDlwMlp6ZCtkdm12T3ZrUllNdnJ4aHZnbm5yV0U9MTswMDsn\"",
                "id": "6c9fec9f-6a40-eb11-a853-d0e7bcc597da",
                "journalId": "f91409ba-1d3d-eb11-bb72-000d3a2b9218",
                "journalDisplayName": "DEFAULT",
                "lineNumber": 30000,
                "accountType": "G_x002F_L_x0020_Account",
                "accountId": "844110b4-1d3d-eb11-bb72-000d3a2b9218",
                "accountNumber": "20700",
                "postingDate": "2020-10-20",
                "documentNumber": "SALARY2020-12",
                "externalDocumentNumber": "",
                "amount": 6750.00,
                "description": "Salaries December 2020",
                "comment": "",
                "taxCode": "NONTAXABLE",
                "balanceAccountType": "G_x002F_L_x0020_Account",
                "balancingAccountId": "00000000-0000-0000-0000-000000000000",
                "balancingAccountNumber": "",
                "lastModifiedDateTime": "2020-12-17T13:20:26.947Z"
            }
        }
    ]
}

As you can see, each operation has a corresponding result in the response, identified by the id. You should always use the id of the individual operation to find the corresponding result. Don’t assume that the results will always be in the same order as the request! They may seem to be in the same order, but the OData standard describes that the results can be in any order.

Each operation result has a status, which is the HTTP status that you would normally get for a single request. It includes the response headers and response body of the individual operation. The response of the batch request itself will always have status 200 if the server was able to read the batch request. Even if the batch request has operations that couldn’t be processed because of an error condition, the batch response status will still be 200. So don’t only look at the response status of the batch request, you need to read the response body to find out the results of each operation. Only when the batch request body is malformed, or you are not authorized, then the whole batch request will fail and you will get a status 500 (malformed batch request) or status 401 (not authorized).

So far it’s really simple, isn’t it? In the next blog posts in this series, I will cover topics like:

  • What happens if an error occurs in one of the requests
  • Process a batch as one big transaction
  • Combine multiple operations types, like POST and GET
  • Define the order of operations
  • Reduce the size of the response payload

Don’t expect 5 additional blog posts… I’m going to combine them of course, as a batch… you get it? 😉


Viewing all articles
Browse latest Browse all 118

Trending Articles