Skip to content

Fulfillments

A fulfillment is used to retrieve, send or process data in APICHAP. Everytime an operation is executed it will run the list of fulfillments specified in your operation. All the data retrieved within a fulfillment will be temporarily stored within the current operation context to use in the upcoming fulfillments and the response.

Typical examples for a fulfillment you might create:

  • A READ fulfillment to SELECT data from a database.
  • A UPDATE fulfillment to create and write into a file on an FTP-SERVER.
  • A READ fulfillment to download a CSV file from an URL.
  • A LOOP fulfillment to loop over an incoming JSON array requestbody.

Depending on the type a fulfillment has a different purpose.

The following types are available:

  • READ: Retrieve data from a datasource.
  • UPDATE: Update data in your datasource.
  • LOOP: Run a list of fulfillments multiple times in a loop.
  • IF: Create an if - else branch and define a list of fulfilments to be run in either the if or else branch.
  • GENERIC: A generic fulfillment can be used to do many things, not related to READ or UPDATE data in a datasource. Usually used to prepare attributes or handle data checks.
  • REFERENCE: A way to execute another operation from the current one.

The types READ and WRITE are mostly very similar and will only make a difference when the instruction has an explicit differently process to read or write data. For example the case when calling an SQL query.

Fulfillments contain the following attributes:

Field Used in Type Description Mandatory
name All Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. x
type All Define the type of your fulfillment. READ, UPDATE or LOOP x
if All Specifies an if condition. The fulfillment should only be processed under certain conditions.
attributes All Define a list of attributes usable in the whole operation context.
exceptions All Use exceptions to immediately return a HTTP response to the user.
sleep All Time in milliseconds the thread will sleep before executing the fulfillment.
datasource READ, UPDATE The datasource this fulfillment should use to retrieve data from. Use the datasource name from the datasources section. x
instructions READ, UPDATE Define your instructions. An instruction is the actual command on how to retrieve data from your datasource. Like a SQL query, Http-Call, ... x
loop LOOP Define a JSONArray to loop through and process each entry with a list of sub fulfillments.
fulfillments LOOP In combination with loop you are able to define a list of fulfillments to run in a loop
loopbreak LOOP A list of conditions. If they match, the current loop is aborted and apichap will continue with the next fulfillment.
index LOOP If you run a fulfillment multiple times in a loop, you are able to index each iteration by a value.
then If Only in combindation with fulfillment type if. Define a list fulfillments executed when the if condition matches to true.
else If Only in combindation with fulfillment type if. Define a list fulfillments executed when the if condition matches to false.
operationName REFERENCE Only in combindation with fulfillment type reference. Define the operation you would like to trigger.
parameters REFERENCE Only in combindation with fulfillment type reference. Define parameters for the operation trigger.

Fulfillment Name

Rules for naming your fulfillments:

  • Fulfillment names are no longer mandatory, however recommended for easier logging.
  • Fulfillment names must not contain dots
  • Spaces are not recommended

IF

Whenever you want to process a fulfillment only on a certain condition. For example, if the input Header-Parameter 'Content-Language' is available. This can be achieved with the if condition.

# Only run fulfillment if Content-Language header is not empty or null
if: PARAM(Content-Language) != NULL()
# Only run a fulfillment if another fulfillment called 'ff1' (already executed) has at least one entry
if: SIZE(VALUE(ff1, $)) > INTEGER(0)

Exceptions

In order to handle error cases you are able to specify exception for each fulfillment. Whenever an exception is triggered the processing of the fulfillments is aborted and the exception´s response is returned directly. It´s possible to define multiple exceptions.

Field Description Mandatory
if Define an if condition under which you want to trigger this exception
statuscode The http status code you want to return for the error case. e.g.: 404 x
message The response message you want to return x

A classical case is to return HTTP-code 404 when an item is not available in the database. Your exception for this case could look like this:

# we have a fulfillment getting a news entry from a database and want to return 404 if the news id does not exist.
name: ffnewsdetail
type: read
datasource: db
instructions:
  - sql: 
      query: Select * from news WHERE id = ?
      parameters: 
        - param: PARAM(id)
exceptions:
  - if: SIZE(VALUE(ffnewsdetail, $)) <= INTEGER(0)
    statuscode: 404
    message: "Item is not available"

Instructions

Instructions are the process to communicate with your datasource and call for informations. instructions are the actual place to specify SQL queries, HTTP-requests, ...

A instruction is only allowed for READ & WRITE fulfillments

Therefore depending on the type of datasource multiple types of configuration options are available:

  1. Database
  2. Request
  3. File

Instructions IF

No matter what kind of datasource you are using you are always able to set a list of instructions with an if condition. This way it´s possible to send different queries depending on certain conditions.

Field Description Mandatory
if Define an if condition under which you want to use this query
query / request / file Depending on your datasource you either have a query, request, ... x

Database

An instruction for a database type is fairly easy as you simply provide the SQL query.

To keep your application secure, use prepared statements with ? and specifying your values in the parameters section.

instructions:
  - sql:
      query: SELECT id FROM news WHERE lang = ?
      parameters: 
        - param: PARAM(Content-Language)

Optional Filters

Often you want to build a SQL query depending on optional filter parameters retrieved. That is possible by using the if in the parameters and preparing attributes. This could be an example on how to build such a SQL with optional filters:

 - name: prepare_filter
     summary: Prepare the sql statement for optional filters.
     type: generic
     attributes:
       - if: PARAM(startdate) != NULL()
         startdateSQL: TEXT(AND e.startdate >= ?)
       - if: PARAM(enddate) != NULL()
         enddateSQL: TEXT(AND e.enddate <= ?)

 - name: get_filtered_data_events
   summary: Get a list of future events including an attendees count based on filters.
   type: read
   datasource: sample-events
   instructions:
     - sql:
         query: SELECT * FROM events WHERE 1 = 1 ${ATTRIBUTE(startdateSQL)} ${ATTRIBUTE(enddateSQL)}
         parameters:
           - if: PARAM(startdate) != NULL()
             param: PARAM(startdate)
           - if: PARAM(enddate) != NULL()
             param: PARAM(enddate)

Database Response

Every result from a fulfillment (also database results) are transformed into a JSON and accessible with the VALUE method.

The result from a database is transformed in the following JSON set to work with:

id title description
1 News 1 Newsttext 1
2 News 2 Newstext 2
[
  {
    "id": 1, 
    "title": "News 1",
    "description": "Newsttext 1"
  },
  {
    "id": 2,
    "title": "News 2",
    "description": "Newsttext 2"
  }
]

A database query always produces a JSON Array, because we don´t know how many results are returned in a query. Meaning in your JSONpath you have to use $[0].val1 if you are sure only one item is returned.

Generated Keys

When running an INSERT or UPDATE statement APICHAP will always get the Generated Key from the database. Depending on your database the Generated Key looks differently:

Database Get Generated Keys
MariaDb If you run an INSERT command with an auto-incremental ID, you can use VALUE(fulfillment_name, $.insert_id) afterward which provides the generated id.
PostgreSQL PostgreSQL will deliver all columns that have been created in the INSERT/UPDATE query. Just like if you would query a select. VALUE(fulfillment_name, $.id)
MSSQL You will get the auto-incremental ID as GENERATED_KEYS. e.g.: VALUE(fulfillment_name, $[0].GENERATED_KEYS))

Request

A request is specified by setting a ´request´ attribute in your yaml.

Within this ´request´ attribute you are able to set the following attributes:

Field Description Mandatory
url The URL for the request to send. x
httpType Your httpType from our supported list: GET, POST, PUT, DELETE. x
contentType The contentType specifies
header Set http header parameter in the header section.
schema Specify an schema to send with the requestbody.
multipart Send a multipart request
formBody Send a formdata request
csvSeparator Set the csvSeperator if you are reading or writing csv.
allowFailure Allow failure of an request. (Default: false)
ignoreCert Disable SSL Check (Default: False)
timeout Change the default timeout in seconds (Default: 10 seconds)
gzip Force to decrypt the response with gzip. (Default: False)

ContentType

Specify which contentType you are planing to send:

  • application/json
  • text/xml
  • application/xml
  • text/csv
  • text/plain

Your request will be automatically transformed in your requested dataformat. Take a look at the Dataformat section to understand the process behind it.

Specify headers by using a ´Key: Value´ approach.

instructions:
  - request:
      url: "https://www.example.com/news"
      httpType: GET
      contentType: application/json
      header:
        - "Content-Language: ${TEXT(en)}"
        - "X-UserId: ${VALUE(ff1, $.userid)}"

Sending a Requestbody

Find more information on how to build your schema here.

instructions:
  - request:
      url: "https://www.example.com/user"
      httpType: POST
      header:
        - "Authorization: ${PARAM(Authorization)}"
      contentType: application/json
      schema:
        email: PARAM(email)
        name: PARAM(name)
        userType: TEXT(user)
        phone:
          list: LIST(1)
          item:
            id: PARAM(phone)

Sending a Multipart Requests

If you need to send a multipart request you can do so by specifying the multipart attribute. This example shows you how you are using an incoming multipart file within the key image and send it to another request.

instructions:
  - request:
      url: https://www.example.com
      httpType: POST
      header:
        - "Content-Type: image/jpeg"
      multipart:
        filepart:
          contentType: TEXT(plain/text)
          file:
            name: TEXT(file.txt)
            content: TEXT("Hello World")
        jsonpart:
          contentType: application/json
          schema:
            hello: TEXT(world)

Sending a Form Data Requests (formBody)

If you need to send a form data request you can do so by specifying the formBody attribute. This section allows you to easily add key-value pairs to your form body request.

instructions:
  - request:
      url: https://www.example.com
      httpType: POST
      formBody:
        key1: VALUE(val)
        key2: TEXT(val2)

AllowFailure

By default when a request does not deliver a sucessfull statuscode the process is aborted. You are able to change that by setting the flag 'allowFailure'. With allowFailure: true the process will continue. Along with a method STATUSCODE() you are able retrieve the result status code of the fulfillment.

# With allowFailure: true, the operation will continue even if the request fails.
- request:
    url: "https://www.example.com/user"
    httpType: GET
    header:
      - "Authorization: ${PARAM(Authorization)}"
    allowFailure: true

With using the STATUSCODE() method after the fulfillment you are able to request the status.

- name: check_status
  type: generic
  exceptions:
    - if: STATUSCODE(ff_name) != 200
      # Handle failure case

File

When using an FTP datasource you should build a file fulfillment. The FTP datasource supports the following variables when working with files:

  • folderPath: The folder path on the FTP server.
  • filePath: The file path (including the file name) on the FTP server.
  • contentType: The MIME type of the file (e.g., application/xml).
  • csvSeparator: If working with CSV files, specify the separator.
  • move: The path to move the file after processing (optional).
  • schema: A map containing the schema for reading or writing files.

Creating a file

- type: update
  datasource: myftp
  instructions:
    - file:
        filePath: /xxx/File_${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}-${ATTRIBUTE(shipmentID)}.xml
        contentType: application/xml
        schema:
          xxxx:

Listing files in a folder

- name: files
  type: read
  datasource: myftp
  instructions:
    - file:
        folderPath: /

Reading a file in a loop

- name: files_loop
  type: loop
  loop: VALUE(files, $)
  fulfillments:
    - name: myproducts
      type: read
      datasource: myftp
      instructions:
        - file:
            filePath: /${VALUE(files_loop, $.filename)}
            contentType: application/xml
      exceptions:
        - if: SIZE(VALUE(myproducts, $.response.xml.artikel)) <= INTEGER(0)
          statuscode: 404
          message: File not available or empty

Moving a file

- type: update
  datasource: myftp
  instructions:
    - file:
        filePath: /${VALUE(files_loop, $.filename)}
        move: /processed/${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}_${VALUE(files_loop, $.filename)}

You can specify the CSV separator using the csvSeparator field, and the content type for CSV files should be text/csv.

Attributes

With this feature, you can define and store attributes (like variables in programming) within each fulfillment. These attributes can hold values such as strings, numbers, or other data types, and can be reused later in the operation.

How to Define Attributes

Attributes can be defined within any fulfillment and will be available throughout the operation after they are created. For example:

- name: update_project_status
  type: UPDATE
  datasource: middleware_db
  instructions:
    - sql: 
        query: UPDATE project SET status = 'failure' WHERE uuid = ?
        parameters: 
          - param: PARAM(projectid)
  attributes:
    - status: VALUE(update_project_status, $[0].status)

In this example, the attribute status is created and assigned the result of the VALUE function, which can be reused later in the operation.

Conditional Attributes

You can also define attributes conditionally by using an if clause inside the attributes section:

- type: GENERIC
  attributes:
    - if: PARAM(lang) == TEXT(DE)
      language: TEXT(GERMAN)
      org_lang: TEXT(DEUTSCH)
    - another_attt: TEXT(test)

Here, the attributes language and org_lang are only set if the condition PARAM(lang) == TEXT(DE) evaluates to true.

Using Attributes

Once an attribute is defined, it can be referenced later in the operation using the ATTRIBUTE function:

ATTRIBUTE(attributeName)

For example, you can use ATTRIBUTE(org_lang) to retrieve the value of the org_lang attribute defined earlier.

Loop

Sometimes you need to run a list of fulfillments in a loop. For example if you have a requestbody and need to process each entry. This is possible by defining a loop with a list of sub fulfillments. A loop fulfillment holds the following attributes:

Field Description Mandatory
name Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. x
type LOOP x
loop Define a JSONArray you want to loop through. x
fulfillments In combination with loop you are able to define a list of fulfillments to run in a loop x

Within a loop you are able to get the current loop json by using the loop´s fulfillment name.

A loop that wants to insert every entry of the POST requestbody array would look like this:

'post /news/bulk':
  fulfillments:
    - name: loop_news_body
      type: LOOP
      loop: BODY($.news)
      fulfillments:

        # Insert the news entry
        - name: news-insert
          type: UPDATE
          datasource: mydatabase
          instructions:
            - sql: 
                query: INSERT INTO news (id, title) VALUES (?,?)
                parameters: 
                  - VALUE(loop_news_body, $.id)
                  - VALUE(loop_news_body, $.properties.title)

Fulfillment Results within a loop are not available outside of the loop.

Loopbreak

Let´s assume you are calling a paged GET /products request in a loop until your paged request delivers no more results. A typical case for a loopbreak. A loopbreak can be set to each fulfillment (not the loop fulfillment directly) and holds a list of conditions. After the fulfillment is ran it checks if any of those conditions are true and if so aborts the loop and continues with the next fulfillments outside of it.

  # Call a paged request by using loop(10) but abort the loop as soon as the request page is empty.
  - name: loop_ff
    type: loop
    loop: LIST(20)
    fulfillments:

      # Retrieve inventory per page
      - name: get_inventory
        type: READ
        datasource: api
        instructions:
          - request:
              url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
              httpType: GET
        loopbreak:
          - SIZE(VALUE(get_inventory, $.inventory)) <= INTEGER(0)

Index

If a fulfillment retrieves data and is run multiple times because its part of a loop you need a way to reference which data you want to access afterwards. Therefore define the index value to an unique identifier. Afterwards you are able to access specific data results of the fulfillment using the VALUE_BY_INDEX method.

  - name: loop_ff
    type: loop
    loop: VALUE(products, $)
    fulfillments:

      # Retrieve inventory per page
      - name: get_inventory
        type: READ
        datasource: api
        instructions:
          - request:
              url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
              httpType: GET
        index: VALUE(@, $.id)

Fulfillment Type: if

The fulfillment type if allows conditional execution of fulfillments based on a logical expression. Depending on the result of the condition, different sets of fulfillments can be run for both the then and else cases.

Do not confuse this with the simple if condition available for every fulfillment. A if fulfillment type is usefull if you have more fulfillment depending on something.

Structure

  • type: if: Indicates that this fulfillment checks a condition.
  • if: The condition to evaluate. If this condition is true, the fulfillments under then will execute. Otherwise, those under else will execute.
  • then: A list of fulfillments to run if the condition is true.
  • else: A list of fulfillments to run if the condition is false.
- type: if
  if: SIZE(VALUE(gen_config, $.data.operations)) <= INTEGER(0)
  then:
    # Config could not be created
    - name: update_project_status
      type: UPDATE
      datasource: middleware_db
      instructions:
        - query: UPDATE project SET status = 'failure' WHERE uuid = ${PARAM(projectid)}
      attributes:
        - status: VALUE(update_project_status, $[0].status)
  else:
    - type: UPDATE
      datasource: middleware_db
      instructions:
        - query: INSERT INTO config_response (project_id, action_generation) VALUES 
            (
              ${PARAM(projectid)},
              ${GET_JSON(gen_config)}
            )

Fulfillment Type: GENERIC

The GENERIC fulfillment type is designed to handle non-data operations, such as checking conditions or setting attributes. It cannot be used to read from or write to a datasource, making it useful for logic-only operations within the flow of an operation.

Usage:

  • No instructions related to reading or writing data are allowed.
  • Primarily used for setting attributes, performing validations, or conditional checks.

Example:

- type: GENERIC
  attributes:
    - key: VALUE(some_value, $[0].attribute)