Skip to main content

API Ninja: Training 5/7 - Advanced Actions and Tricks

Advanced actions in the API — tips, tricks, and techniques offered by the ABRA Flexi REST API (fifth and penultimate training session)

Written by Petr Pech

Skills acquired:

  • knowledge of actions and creation modes supported by the API

  • knowledge of advanced services in the API

We are already experienced ninjas who can handle many complex tasks in the API, but the REST API has even more to offer. Certain actions, operations, and services that would otherwise require workarounds or custom development can be handled through the API using simple techniques, which we will demonstrate in this chapter.

At the basic apprentice level, we will show how to, for example, delete, lock, or cancel a document via the API using an action, and we will also explore different record creation modes. Since we will be modifying data, we will need an application such as Postman. At the Warrior level, you will learn about some services the API provides, saving users from having to perform those actions manually in the application. At the Ninja level, we will go even further and explore a different approach to the API using /query and other advanced services. We will also cover transactional processing at the Ninja level. This technique can be useful when you need to split an import and write each record in its own separate transaction. Get your training ground ready — let's go!

Level: Apprentice

Creation and update modes are controlled via a parameter on the agenda element. In essence, there are two modes:

  • create

  • update

Imagine a scenario where we have a regular import of business partner updates from a CRM into ABRA Flexi and we only want to import updates for existing companies. In other words, we are sending updates for partners that already exist in ABRA Flexi, but we do not want to create new business partners, to avoid duplicates, for example. How do we handle this?

For this, we use creation modes. First, let's walk through the outlined example: we send a company update to the address book while preventing the creation of a new record. We send the request using the POST method to the familiar URL:

The request body contains Flexi XML as we know it, with one small change. We add an action in the form of the parameter create=ignore on the agenda element:

<?xml version="1.0"?>
<winstrom version="1.0">
<adresar create="ignore">
<id>code:NINJAFIRMA</id>
<ic>123456789</ic>
<nazev>Ninja firma s.r.o.</nazev>
...další informace
</adresar>
</winstrom>

And that's all there is to it — <adresar create="ignore"> is the magic that ensures: if a company with the code NINJAFIRMA does not yet exist in ABRA Flexi, the importer will not create it; and if it does exist, its information will be updated.

Simple, isn't it, apprentice?

A similar but stricter example is checking for existence using the action create='fail'. The attribute create='fail' prevents the creation of a new record. This means that importing a company that does not exist will return an error message.

The second mode variant is update. As an example, consider importing sales invoices into ABRA Flexi. Imagine that we want to create new documents but do not want to modify or update any existing invoices. Again, we send the request using the POST method to the URL:

The request body then contains the XML for the sales invoice extended with the parameter update=ignore on the agenda element:

<?xml version="1.0"?>
<winstrom version="1.0">
<faktura-vydana update="ignore">
<id>code:VF1-0123/21</id>
... povinné náležitosti faktury
</faktura-vydana>
</winstrom>

We have simply ensured that if invoice VF1-0123/21 already exists in ABRA Flexi, it will not be modified in any way. If the invoice does not exist, a new one will be created.

Both modes also have a default state — update=ok and create=ok — which are the defaults and do not need to be specified. update=ok updates all existing records but also allows new ones to be created. create=ok creates everything but also updates everything.

Apprentice, can you come up with and test an example using update=ignore?


Those were the creation and update modes. Now let's look at the actions the API offers:

  • action=delete

  • action=storno

  • action=lock

  • action=lock-for-ucetni

  • action=unlock

Actions are techniques for the Ninja that allow you to simulate button clicks in the API just as you would in the application. Imagine trying to cancel a document without using an action. Setting the element storno=true — is that enough? A quick action that also cancels linked documents saves a lot of work compared to doing it without an action.

A request with an action looks similar to one with creation/update modes. Let's jump straight to an example where we want to quickly and easily cancel a newly created invoice. We send the request using the POST method to the URL:

And the body, which a sharper apprentice has surely already figured out:

<?xml version="1.0"?>
<winstrom version="1.0">
<faktura-vydana action="storno">
<id>code:VF1-0123/21</id>
... další informace nejsou potřeba!
</faktura-vydana>
</winstrom>

The ABRA Flexi API will respond that it has updated the invoice, which is now cancelled.

And what about action=delete, apprentice? An apprentice who knows HTTP methods is surely wondering what the difference is between action=delete and the HTTP DELETE method. Both actions are essentially the same at their core. They differ slightly in the behavior prior to executing the action. For example, action=delete allows deletion of documents from periods other than the current one. The DELETE request does not allow this and returns "No permission". action=delete can be used in almost all cases. It cannot be used on users or standard list records, for example. Depending on the type of relationships involved, action=delete will either delete linked records, remove relationships, or prevent the action from being executed.

The last example in today's training for the apprentice is locking. You are certainly familiar with locking from the desktop application — conveniently lock and unlock using a button — but how does it work in the API? The example should now be clear to everyone.

Again, a POST request to the URL:

And the body with action=lock:

<?xml version="1.0"?>
<winstrom version="1.0">
<faktura-vydana action="lock">
<id>code:VF1-0123/21</id>
... další informace nejsou potřeba!
</faktura-vydana>
</winstrom>

Let's extend the locking technique with batch processing. What if we want to lock several invoices at once based on some filter? We simply include the filter as an additional parameter and we're done:

<?xml version="1.0"?> 
<winstrom version="1.0">
<faktura-vydana action="lock-for-ucetni" filter="stavUhrK = 'stavUhr.uhrazeno'">
</faktura-vydana>
</winstrom>

With this request, we locked all sales invoices that are in the Paid status. Note, however, that this time we locked them for everyone except the accountant — specifically, except the user who has the Accountant role.

Time to train, apprentice — there's plenty of room to practice: compare deleting with actions versus deleting with the HTTP method, try batch locking with different filters, and don't forget to unlock so everything in Flexi doesn't stay locked. Good luck!

Level: Warrior

Warrior, in today's training we will explore a number of useful services that allow you to automate many tasks via the API and reduce the manual workload for users in the application. We will cover the simpler services — if the training is not enough, we will introduce more advanced services at the Ninja level. Tighten your belt, let's get to it.

We will focus on warehouse services. Among the most commonly used are: updating dispatch requests, recalculating the warehouse (which is recommended for large companies to be done exclusively via the API), and inventory services. There are also outputs such as the warehouse status as of a specific date, and more.

Let's start with dispatch requests. If dispatch request generation is enabled in the company settings, you can call the Update Warehouse Dispatch Requests service via the REST API. You can use either the PUT or POST HTTP methods for the call.

The request looks as follows:

Simple — the request requires no parameters. Unfortunately, a web browser cannot be used since it sends requests using the GET method. If the service is executed successfully, an HTTP status 200 is returned along with the response:

<?xml version="1.0"?> 
<winstrom version="1.0">
<success>true</success>
</winstrom>

If dispatch requests are not enabled in the company settings, the REST API will inform you with an error status code 400 and the message:

<?xml version="1.0"?> 
<winstrom version="1.0">
<success>false</success>
<message>Není povoleno generovat požadavky na výdej.
</message>
</winstrom>

Another simple yet very powerful technique for every API Warrior is the warehouse recalculation. The warehouse recalculation checks and, if necessary, recalculates the prices on warehouse cards to their correct values. As mentioned, for companies with large warehouse volumes, recalculating via the API is often the only viable option. Again, you can use either the PUT or POST HTTP methods. For warehouse recalculation, there are two options — recalculating the entire warehouse and recalculating a specific warehouse card:

Recalculating the entire warehouse has 2 parameters. The parameter ucetniObdobi is mandatory — you must specify which accounting period the warehouse should be recalculated for. The second parameter sklad is optional; if multiple warehouses exist, all of them will be recalculated. When recalculating via a warehouse card, the period does not need to be specified, as the warehouse card is already period-specific by nature. You can, however, use filtering to retrieve a specific record.

Warrior, where did dostupMj come from? We already know this — it's an agenda field. If you need a refresher, revisit Training 3!

The API response is similar to that of the dispatch request update: success returns 200 - True, failure returns 400 with an error description.

We've updated, we've recalculated — so why not check the results? Next, let's look at the Warehouse Status as of a Date service. The warehouse status as of a date can be retrieved using the HTTP GET method, which means you can save the request in your web browser as a bookmark and call it from the browser at any time:

The warehouse parameter is mandatory — you must specify which warehouse you want to display. The date parameter is optional; if omitted, you will get the current warehouse status. What does the output look like? However you wish, based on the stav-skladu-k-datu agenda. We are already familiar with filtering and detail options from the third training, so let's show a simple example:

Warrior, now it's your turn to check what the output looks like — go for it!

The last services we will cover at the Warrior level are inventory services. What if some stock levels don't add up and you need to run a stocktake? First, we need to create the inventory record. Using the HTTP POST method, we send a request to:

<?xml version="1.0"?> 
<winstrom>
<inventura>
<datZahaj>2021-09-30</datZahaj>
<sklad>code:PLZEŇ</sklad>
<typInventury>Kontrola skladu NINJA</typInventury>
<stavK>stavInventury.zahajena</stavK>
</inventura>
</winstrom>

The only mandatory field is the start date datZahaj, but there is no reason not to include more information to keep your data tidy. Now we have the inventory record and we need to specify which items we will be checking. Imagine that the physical stock count has been completed and we know the actual quantity of a given item. We smartly send the item status to the inventory using a POST request to:

The request body will contain the item and its status. Don't forget to link it to the corresponding inventory record using its ID:

<?xml version="1.0"?>
<winstrom>
<inventura-polozka>
<cenik>code:NINJASŤÍT</cenik>
<mnozMjReal>150</mnozMjReal>
<inventura>3</inventura>
</inventura-polozka>
</winstrom>

A Warrior certainly knows how to retrieve the inventory ID.

We now have the item NINJASHIELD in the inventory with an actual count of 150 pcs. The system count was loaded from the current warehouse status. However, it may happen that the inventory takes a week and during that time stock movements occur. For such situations, we use the Update Statuses service, which is again very straightforward in the API. The service is called using the GET method — no parameters, no filters — you just need to know the inventory ID:

We are almost done. We have the inventoried item, we have the current actual stock level vs. the system stock level. The final step is to generate the inventory differences and reconcile the counts. We use the method vygeneruj-doklady. Again, we use the GET method, but this time one mandatory parameter typDokId is added:

For experienced warriors, retrieving the warehouse movement type ID is certainly a breeze — just as a reminder, the request for these types looks as follows:

This call returns all warehouse document types. Can you refine the call further, Warrior?

The API responds with appropriate messages depending on whether the call succeeds or fails:

Doklady byly úspěšně vygenerovány.

--nebo--

Při inventuře nevznikl žádný inventurní rozdíl.

--nebo--

Na položce s ID = 2 se vyskytla chyba Na skladě není dostatek zboží.

Warrior, that's all for today — get training, it's time to clean up the warehouse!

Level: API Ninja

Ninja, get ready — today's session will be comprehensive and packed with content. We will learn more from the series of services introduced at the Warrior level. Then we will explore how to access the API using /query, and finally we will cover transactional processing.

The REST API offers a wide range of options for payment matching, and we will cover some of them with examples. A cash register or bank transaction can be matched with one or more sales or purchase invoices.

Since we are at the Ninja level, we won't go over creating documents — we've already learned that — and we'll jump straight into matching. Matching is always sent using the POST method to either the banka or pokladni-pohyb endpoint, depending on which documents you want to match. Let's look at an example:

<?xml version="1.0"?> 
<winstrom version="1.0">
<banka>
<id>code:B+001/2021</id>
<sparovani>
<uhrazovanaFak type="faktura-vydana" castka="1000">
code:FV1
</uhrazovanaFak>
<uhrazovanaFak type="faktura-vydana">code:FV2</uhrazovanaFak>
<zbytek>ignorovat</zbytek>
</sparovani>
</banka>
</winstrom>

The first part identifies the bank transaction using the internal number. At this step, you can also create the bank transaction — if you include all the required fields for the banka agenda, you can create the bank movement at the same time. Next comes the actual sparovani. As the element name uhrazovanaFak suggests, this is where you specify which document is being paid. Multiple invoices can be paid in a single matching operation. When matching with multiple invoices, all specified invoices must be of the same invoice type (either sales or purchase). For each invoice being paid, you can specify the attribute castka, whose value limits the total amount to be settled from that invoice. The value of the castka attribute must not exceed the remaining amount due on the invoice being paid.

The next element is zbytek, which can take multiple values, because a remainder may occur when the paying amount on the paying document and the sum of amounts on the invoices being paid do not match (e.g. due to an exchange rate difference or a few missing cents). The possible values are:

  • ne: a remainder must not occur; if it does, the API returns an error

  • zauctovat: the remainder is posted

  • ignorovat: the remainder is ignored

  • castecnaUhrada: if the amount on the paying document is less than on the invoice being paid, it is treated as a partial payment

  • castecnaUhradaNeboZauctovat: if the amount on the paying document is greater than on the invoice being paid, the remainder is posted; if it is less, it is treated as a partial payment

  • castecnaUhradaNeboIgnorovat: if the amount on the paying document is greater than on the invoice being paid, the remainder is ignored; if it is less, it is treated as a partial payment

Ninja, now that you know the principle, you are surely wondering what happens when the currencies of the documents differ. It is possible to match a cash register or bank transaction in the domestic currency with invoices in a foreign currency. A great example for training!

Matching covered — but what if you need to unmatch? That is very straightforward. Analogous to sparovani, there is odparovani (unmatching). POST method, request to the same endpoint banka or pokladni-pohyb, and the request body looks as follows:

<?xml version="1.0"?> 
<winstrom version="1.0">
<banka>
<id>code:B+001/2021</id>
<odparovani>
<uhrazovanaFak type="faktura-vydana">code:FV1</uhrazovanaFak>
</odparovani>
</banka>
</winstrom>


Again, you can specify multiple invoices to unmatch, or none at all — in which case all invoices linked to that bank transaction will be unmatched.

The API also offers automatic matching, which has a wide range of configuration options to make the API Ninja technique as effective as possible. Again, we send a POST request:

We applied a filter for bank transactions issued from 1 October 2021 onwards, along with several parameters that allow us to control the matching. The available options are as follows:

mod – automatic matching mode:

  • varCasUcet: match by variable symbol, amount, and account

  • varCas: match by variable symbol and amount (default value)

  • jenVar: match by variable symbol

  • jenCastka: attach — match when amounts agree but variable symbol does not

obdobi – which accounting periods to search for documents to be paid

  • aktualni: current accounting period

  • aktualni-predchozi: current and previous accounting period

  • vsechna: all accounting periods (default value)

ignorovat-rozdil-castka – how large a difference between the payment and the invoice to ignore (default value 0.0 — amounts must match; in jenVar mode, the difference setting is ignored)

zauctovat-rozdil – whether documents should be posted when payments are linked but amounts differ (default value true — an internal document for the difference between the documents will be created and the documents will be fully matched)

Ninja, there is still plenty to explore in the area of API services — more than this training can cover: ZDD relationships, offsets, VAT returns, period initialization, and more. There's no room for boredom — only for training and improvement!

Using /query, you can send all parameters and filters that are normally included in the URL as part of the request body instead. In this documentation, we will describe the basic functionality. The POST request body can include the detail level, pagination, filtering, and more. For /query, we always use the HTTP POST method. Currently, /query is only available in JSON format. We will show an example using a sales invoice — the standard call looks like this:


The body might look like this, for example:

{ "winstrom": { 

"detail":"custom:kod,nazFirmy,datVyst,datSplat,zbyvaUhradit,
storno,juhSum,sumCelkem,stavUhrK,sumCelkemMen,mena(kod),
stredisko(nazev,kod,id),typDokl(typDoklK)",

"includes":"/faktura-vydana/mena,/faktura-vydana/stredisko,/faktura-vydana/typDokl",

"filter":"(kod like \"2021\" and datSplat lt now() and mena eq \"31\" and typDokl eq \"code:FAKTURA\")",

"no-ext-ids":"true",
}
}

The filter uses logical operators and or or, as well as others as described in the filtering documentation. Meaningful quotation marks are also escaped using backslashes for string literals. You may also notice the now() function, which passes today's date. It is also possible to combine approaches, where some parameters are written into the URL:

The body is then identical:

{ "winstrom": { 
"detail":"custom:kod,sumCelkem,varSym,typDokl,firma(email,tel)",

"limit":"0",

"filter" : "(datVyst > 2021-06-01) and typDokl = \"code:OBP\" ",

"includes":"/faktura-vydana/stredisko",

"order":["sumCelkem","kod"],
}
}

This is a different approach to the API — for example, if you want to retrieve data using POST requests and avoid exposing parameters in the URL where users might see them.

The last topic we will cover in today's training is transactional processing. By default, every import via the REST API is performed as a single database transaction — meaning either everything we send is saved, or nothing is. However, this behavior can be changed using an advanced variant of XML import, which, if used incorrectly, can lead to data inconsistency. We're training, so there's nothing to be afraid of!

Let's jump straight to an example. Imagine we are creating sales invoices and sending the following request:

<winstrom version="1.0" atomic="false">
<skupina-zbozi update="ignore">
<kod>VYBAVENI</kod>
<id>code:VYBAVENI</id>
<nazev>Ninjovské vybavení</nazev>
</skupina-zbozi>
<cenik>
<nazev>Ninjovské ostří</nazev>
<id>code:NINJAOSTRI</id>
<typZasobyK>typZasoby.zbozi</typZasobyK>
<skupZboz>code:VYBAVENI</skupZboz>
</cenik>
</winstrom>

However, a situation may arise where transactional behavior is not necessary. In that case, this behavior can be changed using the attribute atomic. If you set the attribute atomic to the value false, each record will be imported in its own separate transaction. So in the example above, two database transactions will occur — one for the product group VYBAVENI and one for the price list item NINJAOSTRI. If one import fails, the other can still complete successfully.

What is the benefit? When importing large XML files with many records, such as a batch of invoices, the transaction takes a long time and a large amount of information must be kept in memory. Both factors have an adverse impact on performance. However, if each record is independent and it doesn't matter if saving one of them fails (for example, when you repeat the import regularly and/or can intervene manually in case of issues), you can significantly reduce the memory footprint of the import.

That's all for today's training, Ninja — it's time to practice on your own. We recommend trying various combinations of what you've learned today.

No time to waste — you can continue to the final training: custom button

Did this answer your question?