Good evening!
My name is Stanislav Fabiianskyi.
Today, I will be talking about Integrating APIs for Dynamics 365 Business Central.
I will first tell about what it is, why it's necessary,
and then give some examples.
API is created for communication between BC and any other third-party applications.
Such communication occurs via REST API standard,
namely Microsoft protocol OData V4.
Dynamics BC is back-end in this communication,
and third-party applications send requests and are the front end.
To provide BC with access to API,
3 actions are required.
First, enable OData Services and API Services
in settings of NAV Application Service
and integrate API through the client.
I will quickly show these settings.
Going to Administrative Server Console.
OData Services tab.
There are two check boxes:
Enable API Services and Enable OData services.
It's necessary to set them to "True",
save them and reload the instance.
After that, in the client,
we go to API setup page and click the "Integrate APIs" button.
It will set IDs for all standard Microsoft  entities, declare complex types.
It usually takes up to half an hour,
and the table is locked,
so this should be taken into account.
I will now show a demo on how to create API pages
so that we could address them in the future.
I will go to old development environment.
To create a page of API type,
we need to click "New".
A page can be created with Wizard or manually.
You can drag fields there, etc.
After a page is created,
I would like to highlight peculiarities of an API page.
First, page type is API.
Entity Name and Entity Set Name are its most important properties.
These are the names we give to this page
to identify it through API.
It can be called anything in the system,
but in API, it will be recognized as stCustomers.
Another unique characteristic is OData Key Fields property
that should better be set as ID.
This property determines what will uniquely identify the line in API.
This can be both a regular primary key of a table
and a new ID field which is a unique GUID.
According to best practices, it is necessary to use ID
as on part of front end,
it will be more convenient to address the entities
the unique identifier of which will always be an ID.
This way, it is much easier to generate requests.
All other properties are standard, just like in other pages,
and perform the same functions:
Editable, InsertAllowed, ModifyAllowed,
are responsible for allowing of inserting and modifications of lines in a table.
The same refers to field properties:
some of them are modifiable, others not.
Moving on to the second example.
This is a page based on Item table.
These are fields of a regular item.
In terms of properties, it doesn't differ much.
Entity Set Name is stItems.
The ID field is the key.
All names provided to API
must be in camelCase format
meaning the first word is in lower case
and the rest are capitalized.
This is a mandatory condition.
This also applies to the names of entities, fields,
and actions that we'll create.
In NAV 2018,
regardless of what name you give to an action,
Microsoft always published it capitalized (PascalCase).
After switching to BC,
they started using lower-case letters.
Therefore, all applications using methods were broken.
It was necessary to look for the reason.
In this example, I also wanted to show
that a sub-page can be included in an API page.
To do that, it is necessary to create a control page part,
indicate which page we are addressing,
link it,
and give it a unique name.
In this case, it will be a page
that will show if an item is available for buying online.
This sub-page contains just 2 fields.
It's similar to other sub-pages in terms of properties.
Its type is "ListPart".
It is also necessary to indicate OData key fields
for unique identification of a line.
A few words about communicating with these pages.
4 methods can be used through API:
GET, POST, PATCH and DELETE
for reading, inserting, modifying and deleting lines, respectively.
Let's start with GET method.
I will show examples on how to address it.
The main url is the url to all entities
that are available out-of-box.
It is necessary to write a server name,
port OData,
instance name
and two static words: API and beta.
After sending a request,
you'll get a response in JSON format
with the list of all available entities
provided by Microsoft.
If we search further,
newly created entities will be available.
To address them,
they must be specified in url.
For example, I want to get a list of all items
from my newly created page.
To do that, I first need to select a company, just like in a regular client,
because all tables are divided into companies by data.
To select a company,
we first need to address the Companies entity.
After a standard url, I add "companies".
Sending a request.
In response, I get a list of all companies available to me.
After that, I need to choose it.
To choose a line, a record,
it is necessary to indicate its unique identifier.
In this case, it's Field ID.
Copying the Company ID.
Inserting it in round brackets.
By doing this, I ensure that I receive data for this company only.
To address a list of items,
I have to write the name of this entity after slash.
After writing a name, I send a request.
I get a big list of all items available in the system.
To choose a specific item,
it is also necessary to type item ID after entity name in round brackets.
In response, we'll get the chosen item only.
I showed there is a sub-page
with information on if an item is available for buying online.
As of now, there hasn't been any information on it.
To get information on if an item is available for buying online,
it is also necessary to address its sub-page.
We selected an item, specified its ID
and then wrote the name of an entity of my sub-page:
stItemsBuyableOnline.
After sending a request,
I can see there are 3 lines connected with this item.
It is also possible to get data about an item
and if it's available online
in one request.
For that, Expand parameter is used.
Instead of a regular slash,
we indicate an Expand parameter
and specify the pages that we want to expand and show with "=".
With such request,
we'll get main fields of the item and fields of sub-pages.
As this API is implemented based on OData V4 protocol,
most features of OData V4 are available here,
such as filtering, sorting, sampling of first / last 1000 items, etc.
In this example, I'm showing how sampling can be filtered.
We need to specify the Filter parameter.
Then, a field by which we want to filter after a "=" sign.
After that, "greater than".
Special symbols can be checked in OData specification.
Next, specifying the values
to show the items with changes older than 3 December 2016.
Next, "and" with a space
and the price greater than or equal to 10.
By sending this request,
I will only see the items matching the filters in the list.
Pagination is an interesting API property.
Getting back to the url with simple sampling of all items.
Getting to the bottom of request body.
There is a line with OData nextLink name
and with some url in its value.
When I address items
only first 1000 items are returned.
This is due to a setting in NAV Application Server
called Max Page Size.
By default, it's 1000.
It means that, when sampling any entity,
the maximum number of lines that API can return in one request is 1000.
To move on to the next 1000,
the following url is generated.
To understand how it's generated,
we can just see in what it's different from the current url.
In this case, after entity name,
there are Skip Token parameter and ID.
This ID is the last read ID
from my current request.
If I follow the url and click Send,
I'll get sampling of next 1000 items.
I will now show how to work with POST, PATCH and DELETE requests.
I will use a Customer entity.
To create a customer through API,
it is necessary to choose a POST request method
and address the entity we want to insert into.
In header, it is necessary to type Content Type and Application JSON.
This means I will be sending data to API in Application JSON format.
In the request body, in JSON form,
it is necessary to write a customer I want to insert.
In this case, it will be the first customer, name: My New Customer,
a telephone number, city: Kyiv.
If a request is executed successfully,
the status will be 201: Created
and the created customer will be received in response.
Let's go to the client and check it.
A new customer has been created.
To modify a customer,
it is necessary to use a PATCH method.
As you now, in a regular NAV client,
we can only modify 1 line at a time.
Modification of multiple lines is possible only through code.
The same applies to API interface.
To modify a line,
we first need to uniquely identify it.
To do it, I send a GET request on all customers.
Finding a newly created customer.
Copying its ID
and inserting it in round brackets.
Let's imagine I want to change its name.
Selecting a PATCH method.
In the request body,
I specify the field that I want to modify and its value in JSON format.
If I try to do it now, I'll get a successful response
and the name will be modified.
In this request, if-Match parameter is also used.
This parameter is responsible for line state.
In this case, the line state does not matter.
I wrote an asterisk (*) and sent a request.
To modify it correctly,
it is first necessary to get this customer.
There is a field with a value called etag
that is responsible for line state.
In a regular client,
if two users open one customer,
the first user modifies something there
and the second user is also trying to modify the same line,
an error will be displayed
saying "Sorry, refresh the page and repeat".
The same happens here.
This unique set of symbols is responsible for the current line state.
To modify this line, I need to copy this value,
insert it as a value in the if-Match header,
delete back slashes,
and send the request.
Thus, it has also changed.
But if another user modifies it before me, I'll get an error.
In case of deleting a customer,
a url request looks just like the previous one.
The only difference is that we use a DELETE method.
I will copy ID of the customer I want to delete
and insert it in the request.
In case of successful deletion,
I get the following result:
204: no content.
Nothing to show, as it's been deleted.
If I go to the client, I see it's disappeared.
Let's consider Bound actions.
From BC standpoint,
this is a function that is published
and performs certain actions with a record.
There are Bound actions and Unbound actions.
Bound actions are actions with a record,
while Unbound Actions are just a function in Code Unit
which is not tied to any record.
Going to development environment.
I have created a function in Customers page.
To make it available through API,
it is necessary to indicate that it's not local,
it's available outside the object,
as well as set Function Visibility to "External"
and Service Enabled to "Yes".
This makes the function available through API and web services.
In the function, it is possible to write any code.
You can also specify the Action Context parameter
that will be responsible for the response that we'll send
in response to the request.
In BC, there are 3 available options:
201: Created,
200: OK, or 204: No content.
In this case, the function is called Set Default Name.
It sets the value of name to default one
and sends a "201: Created" response.
No, all error codes are returned automatically.
If authentication or authentication type was incorrect,
you'll get a "401: Unauthorized" response code.
In case of internal server error,
you'll get an error with code 404, etc.
It is impossible that it doesn't return anything.
It returns by VAR.
Then it will be a default value: "200: OK" status code.
I will now show how to do it in Postman.
I will create a customer again.
Copying its ID.
Next, it is necessary to specify the entity we address (customers) in url,
specify ID of the customer we're addressing.
Next, to specify the function we're addressing,
it is necessary to write /microsoft.nav. (standard words)
and the function name.
POST method is used for sending a request.
In case of success, if no errors occur,
a "201: Created" response code is returned.
The name has actually changed to a default one.
Let's consider another example where I'll show
how it's possible to use bound actions with incoming parameters
and return something at the function output.
In this case, it's an Example function
that accepts text,
does certain manipulations to it
and then returns a text.
In this case, a name, an incoming parameter
and the word "Completed" will be added.
In Postman,
I specify the customer I want to address
and the function name with a slash.
To pass a parameter to this function, it is necessary to insert it somewhere.
It is inserted into a request body.
With Key Value Pair, we insert an "InParam" variable
with "Example ..." value In JSON format, in curly brackets.
After sending such request,
a standard "200: OK" status code is returned.
In response body,
a text combined with "Example" and "Completed" words is returned
(that's what I specified).
If, due to some reason, an error occurs in NAV while executing code,
there's no need to handle it in any way.
It will be automatically displayed for a user.
If I write an error with a message here
and try to send it again,
I'll get code "400", which means an error,
and the error text.
Standard NAV errors will also be displayed here.
No, any type can be returned.
Except blob, probably.
Several remarks.
First, all requests can be processed on the BC side
by writing code in triggers or subscribing to events.
Just like with regular pages.
We can perform validations, etc.
Second, using camelCase is mandatory, as I've already mentioned.
Permissions for these pages must be issued separately.
If a new page is created,
it must be added for users in Permission Set.
If we address code units, other pages inside the page,
a user sending the request must have permissions for all these objects.
Error handling is automatic.
We don't need to write status codes, messages, what went wrong, etc.
The structure is created with a page
and a sub-page can be added.
As a result, we have a two-level structure.
This limits API capabilities a bit.
Thus, Complex Types were created.
On part of BC, this will just be a variable declared as an array.
Next, it must be registered in the client.
This way, there is an array instead of a regular node.
I had an example here.
Just like address in Customer:
address is actually not a field,
but street, city, state, postal code are all customer fields
there were combined into one complex type, Address.
On the page, there is a PostalAddressJSON variable.
This variable has already been declared as a Microsoft complex type.
There is a page called OData EDM definitions
where complex types are specified.
In my case, I used Portal Address.
New types can be created in it.
An array is declared here in XML format.
In case of sales order,
it is better to create a main API page for sales order
and then a sub-page for sales order lines.
It will then work the way you described.
In one request, a header and all lines contained in the body will be inserted.
If in sales line, there is an address field
that we want to extend in order to show a street, city, etc.
(in sales line, there are no such fields),
complex types will have to be used:
creating a three-level structure, etc.
Here are some best practices.
First, keeping API pages as simple as possible.
This means adding as few fields as possible, adding no code (preferably),
setting everything with properties,
so that it's easy to work with it
and front-end specialist could sort out the functionality easily.
Don't expose internal NAV fields.
After API is published,
no breaking changes should be made.
This means that, as soon as a third-party application integrates with BC,
if an entity name or field name are changed,
the application will break,
as it will address the old names.
By entity name.
If I want to insert a line
and the field is not Name, but Name1,
insertion will be impossible.
It will work for reading, but not for inserting.
Next, it's necessary to think from both sides:
from a NAV developer's standpoint and from a user's standpoint.
Fields must be named logically,
so that even a user who does not know standard NAV entities
could understand what they are addressing and what is received in response.
Lastly, APIs must return fast.
If it takes a minute to wait until all system items are returned,
no one will be waiting for such a request.
All requests must be fast
and as few fields as possible must be used in them.
Any questions?
In case of a bound action,
is it possible to get a header of a request that we're receiving?
Do you mean getting it in BC code?
This is not allowed in API.
Header has a limited functionality
which is standard for all entities.
You can't add own values and headers there.
This API is available starting from NAV 2018.
I think from the first cumulative update and later.
In 2017, it is possible to work this way only through OData Web Services.
If there are no more questions, thank you for coming.
