Customising skill behaviors in OpenAPI documents

You can use additional extensions to modify certain behaviors within IBM Watson Orchestrate™ after establishing the foundation for your skill in the OpenAPI specification.

By editing the available annotations or OpenAPI configurations, you can modify the behavior of the following broader themes:

  1. Viewing skills in the UI
  2. Running skills
  3. Working with user input
  4. Dynamically add field values in the dropdown
  5. Working with skill output
  6. Returning user-friendly errors
  7. Sending data to your API
  8. Sample OpenAPI specification

Viewing skills in the UI

You can use the following options to customize how your skills are displayed to your users in the skill catalog:

Define an application to house your skills

When you assign skills to an application, they are grouped with that application and they use a singular connection configuration for all the skills in that application. For your skills to work, you must have a singular application defined in an OpenAPI specification and the application ID must be unique to your tenant.

You can define an application using the following annotations:

  • You can use the annotation x-ibm-application-id to uniquely identify the application to which the skills in the OpenAPI document belong to.

  • The annotation x-ibm-application-name is the name of the application that the user sees in the user interface. The value must be human-readable and easily understandable.

You can see how the preceding annotations are rendered in Watson Orchestrate in the Figure 1: Viewing skills in the UI.

For your reference, the following snippet is an example:

  • 
    openapi: 3.0.0
    info:
        title: some-name
        version: 1.0.0
        x-ibm-application-id: "salesforce"
        x-ibm-application-name: "Salesforce"
        {: codeblock}
    
  • 
    {
       "openapi": "3.0.0",
       "info": {
          "title": "some-name",
          "version": "1.0.0",
          "x-ibm-application-id": "salesforce",
          "x-ibm-application-name": "Salesforce"
       }
    }
    

Provide a name and description for your skills

For your skills to display suitably, you must include values in the standard optional OpenAPI specification fields:

  • Skill tile: Use the fields summary and description to display the skill in the skill catalog. You can find the summary of the skill displayed next to the icon on the tile and the description is displayed on the side view of a selected skill.

You can notice how the preceding annotations are rendered in Watson Orchestrate in the Figure 1: Viewing skills in the UI.

For your reference, the following snippet is an example that demonstrates sample content for the "Repeat a message" skill:

  • 
        paths:
          /repeatMessage:
            description: "Repeat a message"
            post:
              summary: "Repeat a message"
              description: "Repeats a message that has already been sent."
              operationId: "repeatMessage"
              requestBody:
                content:
                  application/json:
                    schema:
                      $ref: "#/components/schemas/repeatMessage_input"
                required: true
    
  • 
    {
       "paths": {
          "/repeatMessage": {
             "description": "Repeat a message",
             "post": {
                "summary": "Repeat a message",
                "description": "Repeats a message that has already been sent.",
                "operationId": "repeatMessage",
                "requestBody": {
                   "content": {
                      "application/json": {
                         "schema": {
                            "$ref": "#/components/schemas/repeatMessage_input"
                         }
                      }
                   },
                   "required": true
                }
             }
          }
       }
    }
    
  • Skill form: You can provide the JSON schema standard fields title and description inside your response and request schemas. These values appear as field names and helper text on the skill's form.

The following example demonstrates sample content from a Box form:

  • 
          File:
            type: object
            properties:
              id:
                title: ID
                description: Box's unique string identifying this file.
                type: string
              parent_id:
                title: Parent ID
                description: The ID of the parent folder.
                type: string
              name:
                title: File name
                description: The file name, including the extension.
                type: string
                ...
    
  • 
    {
       "File": {
          "type": "object",
          "properties": {
             "id": {
                "title": "ID",
                "description": "Box's unique string identifying this file.",
                "type": "string"
             },
             "parent_id": {
                "title": "Parent ID",
                "description": "The ID of the parent folder.",
                "type": "string"
             },
             "name": {
                "title": "File name",
                "description": "The file name, including the extension.",
                "type": "string"
             }
          }
       }
    }
    

For more information about content best practices in your UI, see UI content best practices.

Provide an icon for your application

You can use the annotation x-ibm-application-icon to add an icon for your application. The annotation can be specified at the info or operation levels in your OpenAPI specification. The icon specified at the operation level takes precedence over the info level.

The annotation takes an HTML <svg> as its value, and is rendered at 48 x 48 pixels. You can create your <svg> using any image modification program, then save it as an SVG file, ensuring that the dimensions are of 48 x 48 pixels.

For example, you can use Inkscape to create an <svg>, either through their GUI or command line tools.

For your reference, the following snippet is an example to do it on the MacOS command line:

brew install inkscape

inkscape -l --export-filename=<your_svg>.svg <your_png>.png

Note:

  • The command opens a GUI, click and choose the Embed option.

  • The conversion maintains the original size and the UI renders a scaled-down image. To conserve space in your OpenAPI it is recommended to produce a 48 x 48 pixel version of your icon before conversion.

After you create and save your SVG, complete the following steps to add it to your application:

  1. Format your svg to avoid the usage of any classes. For example, you can use the tool svgo to format <svg> using the following command.
npx svgo <your_svg>.svg -o <your_svg>.svg

Note: Make sure to install Node.js on your system.

  1. Run the following command to escape the quotations.
sed 's/"/\\"/g' <your_svg>.svg > <your_svg>-escaped.svg

You can also use an equivalent to escape the double quotation marks (") in the SVG and write to a new file.

  1. Remove any extraneous data and delete anything before or after the <svg> tags.
  2. Embed the image in your OpenAPI specification. Insert the contents of the SVG file into the OpenAPI in the field x-ibm-application-icon. For JSON, you must format the data on a single line. You can do it using the tool yq4.
yq4 -i -o json ".info.x-ibm-application-icon = \"$(cat <your_svg>-escaped)\"" <your_OpenAPI>.json
Tag Required
x-ibm-application-icon No

You can see how the preceding annotation is rendered in Watson Orchestrate in the Figure 1: Viewing skills in the UI.

The following example demonstrates the usage of the info level:

  • 
    openapi: 3.0.0
    info:
        title: some-skill
        version: 1.0.0
        x-ibm-application-icon: <svg width="48" height="48"><circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" /></svg>
    
  • 
    {
        "openapi": "3.0.0",
        "info": {
            "title": "some-skill",
            "version": "1.0.0",
            "x-ibm-application-icon": "<svg width=\"48\" height=\"48\"><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"green\" stroke-width=\"4\" fill=\"yellow\" /></svg>"
        }
    }
    

The following example demonstrates the usage of the operation level:

  • 
    paths:
        /repeatMessage:
        description: "repeat a message"
        post:
          description: "repeat a message"
          operationId: "repeatMessage"
          x-ibm-application-icon: <svg width="48" height="48"><circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" /></svg>
          requestBody:
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/repeatMessage_input"
            required: true
    
  • 
    {
        "paths": {
            "/repeatMessage": {
                "description": "repeat a message",
                "post": {
                    "description": "repeat a message",
                    "operationId": "repeatMessage",
                    "x-ibm-application-icon": "<svg width=\"48\" height=\"48\"><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"green\" stroke-width=\"4\" fill=\"yellow\" /></svg>",
                    "requestBody": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/repeatMessage_input"
                                }
                            }
                        },
                        "required": true
                    }
                }
            }
        }
    }
    
Figure 1: Viewing skills in the UI

View skills in the UI

Figure 1 illustrates how the use of these extensions is reflected in the Watson Orchestrate user interface.

Item Description
1 Define an application to house your skills
2 Provide a name and description for your skills
3 Provide an icon for your app.

Running skills

You can use the following options to determine how your skills are used by your users:

Train the natural language model

You can use the natural language model to determine whether the user's utterance in the UI chat box matches a specific skill. Using the annotation x-ibm-nl-intent-examples you can supply example utterances to train the NL model.

If the annotation is not supplied, utterances are generated from the description and summary that are supplied for the skill. However, these utterances are basic and tend to produce a narrow model.

As shown in the following example, you can specify the extension at the operation level to enable the explicit use of natural language sentences. Ideally, you should provide around 20 to 30 utterances for your skill. You can supply less, but providing fewer than 10 limits the usability of your skill:

  • 
    paths:
        /repeatMessage:
        description: "repeat a message"
        post:
          description: "repeat a message"
          operationId: "repeatMessage"
          x-ibm-nl-intent-examples:
            - "repeat a message"
            - "can you echo this message?"
            - "repeat what I say"
            - "can you repeat what I tell you?"
            - "repeat this message for me"
            - "repeat after me"
            - "say it back to me"
            - "play that again"
            - "repeat this message again"
            - "repeat this message"
          requestBody:
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/repeatMessage_input"
            required: true
    
  • 
    
    {
    "paths": {
    	"/repeatMessage": {
    		"description": "repeat a message",
    		"post": {
    			"description": "repeat a message",
    			"operationId": "repeatMessage",
    			"x-ibm-nl-intent-examples": [
    				"repeat a message",
    				"can you echo this message?",
    				"repeat what I say",
    				"can you repeat what I tell you?",
    				"repeat this message for me",
    				"repeat after me",
    				"say it back to me",
    				"play that again",
    				"repeat this message again",
    				"repeat this message"
    			],
    			"requestBody": {
    				"content": {
    					"application/json": {
    						"schema": {
    							"$ref": "#/components/schemas/repeatMessage_input"
    						}
    					}
    				},
    				"required": true
    			}
    		}
    	}
    }
    

    }

utterance

The utterance is the text that is used to start the skill.

For example,

  • 
    paths:
        /repeatMessage:
        description: "repeat a message"
        post:
          description: "repeat a message"
          operationId: "repeatMessage"
    
    
  • 
    	{
        "paths": {
        "/repeatMessage": {
          "description": "repeat a message",
          "post": {
            "description": "repeat a message",
            "operationId": "repeatMessage",
    
          }
        }
        }
    }
    

Skill input

You can use the following options to interact with how your user is adding content with their skill set:

Understand the input form

The form that is presented to the user when a skill is started is determined by the request body schema defined in the OpenAPI specification for that skill. The process of providing the necessary values is known as slot filling. You can use the following extension schema syntax to control how the form is presented to your user.

All of these fields are supplied inside the OpenAPI schema:

  • 
    requestFeedback_input:
        type: object
        required:
        - addressee
        properties:
        addressee:
          type: string
          title: Addressee
          description: email address of the receiver/assignee
          x-ibm-show: true
        subject:
          type: string
          description: Subject to be reviewed
    
    
  • 
    {
       "requestFeedback_input": {
          "type": "object",
          "required": [
             "addressee"
          ],
          "properties": {
             "addressee": {
                "type": "string",
                "title": "Addressee",
                "description": "email address of the receiver/assignee",
                "x-ibm-show": "true"
             },
             "subject": {
                "type": "string",
                "description": "Subject to be reviewed",
    
             }
          }
       }
    }
    

required

You can use the field required as standard syntax in an OpenAPI schema to indicate that the particular field must be supplied to start the API. Using required, you can ensure that the skill set gets the necessary information. You can add the field required on a wrapping object or on a field.

A wrapping object for example:

  • 
        type: object
        required:
        - addressee
        properties:
        addressee:
          type: string
    
  • 
    {
       "type": "object",
       "required": [
          "addressee"
       ],
       "properties": {
          "addressee": {
             "type": "string"
          }
       }
    }
    

On a field:

  • 
        type: object
        properties:
        addressee:
          type: string
          required: true
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "required": true
          }
       }
    }
    

description

The field description is standard syntax in an OpenAPI schema that is used for describing the use and purpose of a field. It is displayed to the user in the form:

  • 
        type: object
        properties:
        addressee:
          type: string
          description: Provide the addressee for this email
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "description": "Provide the addressee for this email"
          }
       }
    }
    

x-ibm-label

You can use the IBM extension x-ibm-label to supply a human-readable name for input parameters and request body.

For example,

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-label : Addressee
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-label": "Addressee"
          }
       }
    }
    

x-ibm-show

You can use the IBM extension x-ibm-show to show or hide an optional input in the generated form. By default, all of the fields are shown:

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-show : true
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-show": "true"
          }
       }
    }
    

x-ibm-disable

You can use the extension x-ibm-disable to disable a field in the form and make it uneditable. By default, all of the fields are enabled:

  • <pre><code>
    type: object
    properties:
    addressee:
      type: string
      x-ibm-disable : "true"
    

  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-disable": "true"
          }
       }
    }
    

x-ibm-list

The extension x-ibm-list controls how enumerations are displayed to the user. The following values are supported:

  • drop-down: Used for single selection.
  • multiselect: Used for selecting multiple values.

For example,

  • 
        type: object
        properties:
        status:
          type: string
          enum:
            - Success
            - Failure
          x-ibm-list : "dropdown"
    
  • 
    {
       "type": "object",
       "properties": {
          "status": {
             "type": "string",
             "enum": [
                "Success",
                "Failure"
             ],
             "x-ibm-list": "dropdown"
          }
       }
    }
    

x-ibm-prompt

The extension x-ibm-prompt specifies a human-readable question that can be used by a Watson to request input from a user during a conversation:

  • 
        type: object
        properties:
        city:
          type: string
          x-ibm-prompt : "What is the city name?"
    
  • 
    {
       "type": "object",
       "properties": {
          "city": {
             "type": "string",
             "x-ibm-prompt": "What is the city name?"
          }
       }
    }
    

x-ibm-important

You can use the extension x-ibm-important to promote optional fields from the initially hidden section of the form to the main section. Any field that is not required or important is initially hidden from the user in the form that the skill set generates. By default, the value is false.

The syntax corresponds to that of the required attribute. For a wrapper object, use the following syntax:

On a wrapping object:

  • 
        type: object
        x-ibm-important:
        - addressee
        properties:
        addressee:
          type: string
    
  • 
    {
       "type": "object",
       "x-ibm-important": [
          "addressee"
       ],
       "properties": {
          "addressee": {
             "type": "string"
          }
       }
    }
    

On a field:

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-important: true
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-important": true
          }
       }
    }
    

x-ibm-multiline

The extension x-ibm-multiline switches the text box used in the form for the input field from a single line to a multiline box:

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-multiline: "true"
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-multiline": "true"
          }
       }
    }
    

Skill input Figure 4: Understand the input form

Figure 4 illustrates how the use of extensions title, description, x-ibm-multiline and so on are reflected in the input form that is presented to your user.

Upload files

Many skills might potentially require users to upload files as part of the skill, for example, uploading a job description text file. To do so, the input form must display a suitable file upload widget to the user. The uploaded content can then be passed to the API.

The skill set provides a file upload widget to the user that takes in a single file or multiple files depending upon the schema in the OpenAPI specification and the annotation x-ibm-content.

Upload a single file

To create a single file upload, you must define a property with type: "string" and format: binary and use the x-ibm-content with an empty value:

  • 
    content:
        application/json:
        schema:
          type: "object"
          properties:
            myFile:
              type: "string"
              format: "binary"
              x-ibm-content:
                value: ""
    
  • 
    {
        "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "myFile": {
                "type": "string",
                "format": "binary",
                "x-ibm-content": {
                  "value": ""
                }
              }
            }
          }
        }
        }
    }
    
    

This annotation presents a file upload widget to the user that accepts a single file and sends a JSON payload to the API with the content of the uploaded file as a base64 encoded string.

You can use multipart/form-data instead of application/json and the data is sent to the API as a multipart/form-data with the field name as the key, however, currently you might notice issues with the content type if you send more than just the file content.

Upload a file Figure 5: Upload a file

Figure 5 shows an example on uploading a file.

Upload a fixed number of files

Sending a fixed number of files is also possible, using the same method as the previous example. It creates a form with a file upload widget for each file upload defined in the OpenAPI specification:

  • 
    content: 
        application/json:
        schema:
          type: "object"
          properties:
            myFile1:
              type: "string"
              format: "binary"
              x-ibm-content:
                value: ""
            myFile2:
              type: "string"
              format: "binary"
              x-ibm-content:
                value: ""
    
  • 
    {
       "content": {
          "application/json": {
             "schema": {
                "type": "object",
                "properties": {
                   "myFile1": {
                      "type": "string",
                      "format": "binary",
                      "x-ibm-content": {
                         "value": ""
                      }
                   },
                   "myFile2": {
                      "type": "string",
                      "format": "binary",
                      "x-ibm-content": {
                         "value": ""
                      }
                   }
                }
             }
          }
       }
    }
    

The preceding example displays two file upload widgets to the user, one for myFile1 and the other for myFile2.

You can use multipart/form-data instead of application/json, however, currently you might have issue with the content type if you send more than just the file content.

Upload multiple files

You can create a multifile upload widget that lets users upload any number of files to the API. The following API specification defines the file upload inside an array object. You must also add the field fileName:

  • 
    content:
        application/json:
        schema:
          type: object
          required:
            - attachments
          properties:
            redundant:
              type: boolean
            attachments:
              type: array
              items:
                type: object
                properties:
                  fileName:
                    type: string
                    title: "File name"
                  content:
                    type: string
                    format: binary
                    x-ibm-content:
                      value: ""
    
  • 
    {
       "content": {
          "application/json": {
             "schema": {
                "type": "object",
                "required": [
                   "attachments"
                ],
                "properties": {
                   "redundant": {
                      "type": "boolean"
                   },
                   "attachments": {
                      "type": "array",
                      "items": {
                         "type": "object",
                         "properties": {
                            "fileName": {
                               "type": "string",
                               "title": "File name"
                            },
                            "content": {
                               "type": "string",
                               "format": "binary",
                               "x-ibm-content": {
                                  "value": ""
                               }
                            }
                         }
                      }
                   }
                }
             }
          }
       }
    }
    

The field redundant is supplied as without it the skill set immediately supplies an empty array, which is a valid value for an array. The field redundant is not marked as required, hence it doesn't show up in the form without expanding it but might be hidden further by using x-ibm-show.

Note: Currently, you can't upload multiple files with a multipart or form. This is due to the need to supply the extra field, which causes the multipart or JSON conflict noted in the preceding sections.

Dynamically add field values in the drop-down

The OpenAPI specification supports adding a drop-down list into your skill with field values that are dynamically generated from another skill that you have in your skill set. The values can be fetched from an API that stores the data. This is advantageous if the data set you want to include in the OpenAPI specification is large or if it varies over time.

The following image illustrates a custom skill that has a drop-down list as an input. To dynamically populate the drop-down, the Retrieve folders skill from the Dropbox app is used.

x-ibm-ui-extension

The x-ibm-ui-extension extension defines an input as a drop-down list, it includes the skill that populates this list and the labels and values to gather if the skill has complex objects (nested objects):

"x-ibm-ui-extension": 
  {
    "component": "dropdown",
    "actions": [
      {
        "skill_id": "<skill_id>",
        "type": "data",
        "mappings": 
          {
            "labels": "<link_to_object_property_name>",
            "values": "<link_to_object_property_name>"
          },
        "params": {}
      }
    ]
  }

In the extension schema, you have the following keywords and sections:

  • component
    It must be dropdown.

  • actions
    This section defines the skill id, the type as data, and the mappings of the skill that you want to pull data to a drop-down list.

Important:The x-ibm-ui-extension extension works only if the skill defined in skill_id doesn't require inputs.

skill_id

The skill_id is the ID of the skill. Get the title, version, and operationId properties to build the skill_id from the OpenAPI documents for custom skills, or refer to the Skill IDs for built-in apps section for a list of skill-ids for built-in apps.

The IDs are constructed in the following format:

<info.title>__<info.version>__<paths[].path.operation.operationId>

Example:

Translator-API__1.0__translator.skill

Any spaces in each section are replaced with hyphens, and the sections are separated by two underscores. For example, the skill_id for "Translator API" would be Translator-API__1.0__translator.skill.

mappings

The mappings section specifies the values and labels properties, which define how the data that is gathered from a specific skill can populate a drop-down list:

  • values

    Required property. The data sent to the API server when users select an option in the drop-down list. If you add only this property, the data also appears on the user interface (UI) to users as the drop-down labels.

  • labels

    Optional property. The data that is shown to users when they open the drop-down list in the user interface (UI).

To populate the drop-down list, you must create a link in the values and labels properties to an object in the request or response body. For reference, see Linking to operations in the following topic.

Linking to operations

In both the values and labels properties, you must create a link to an object property in the request or response body. The link defines which body contents must populate the drop-down list.

The response from the API that you link can be a simple object or complex objects with arrays of many objects or arrays of simple types. For complex objects, you need to specify the mappings section, otherwise this section isn't needed.

Create the link based on the hierarchy of object properties under the properties keyword. Link's structures might differ based on how an object is built:

# Link to a simple object

<object_property_name>
# Link to a simple object with multiple properties

<object_property_name>.properties.<object_property_name>
# Link to a complex object that nest an array

<object_property_name>.items
# Link to a complex object that nest an array of objects

<object_property_name>.items.properties.<object_property_name>

For the following example, consider the Translator and Languages APIs for a simple operation to translate some text based on a specific language.
The Translator API has the translator endpoint that defines an input as a drop-down list for users to select which language they want to translate their texts. This endpoint links to the Languages API to dynamically generate a list of languages available to translate texts.

So, to define this input as a drop-down list, this endpoint uses the x-ibm-ui-extension extension and adds the mappings section to define which labels and values must populate the list:

"/app/translator":{
  "post": {
    "summary": "Translate a text",
    "operationId": "translator.skill",
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
                "type": "string",
                "x-ibm-ui-extension": {
                  "component": "dropdown",
                  "actions": [
                    {
                      "skill_id": "Languages__1.0.0__language.skill",
                      "type": "data",
                      "mappings": {
                        "labels": "language_options.items.properties.name",
                        "values": "language_options.items.properties.code"
                      }

The Languages API has the languages endpoint that returns an array of complex objects for language options as a response:

"/app/languages": {
  "get": {
    "summary": "Get a list of languages.",
    "operationId": "language.skill",
    "responses": {
      "200": {
        "description": "Success to get the list of languages.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "language_options": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "code": {
                        "type": "string"
                      }

The following image illustrates the mappings section in the Translator API by showing how the link is created based on the response of the languages endpoint in the Languages API:

Example of a working skill with dynamic drop-down list

For your reference, the following files are a working sample of the OpenAPI. The first OpenAPI defines the Manage Job Requisition app, and the second one defines the Getter app.

The following code is a working sample of the Manage Job Requisition app in YAML and JSON. Use this code to create an OpenAPI file and add this app to Watson Orchestrate:

  • openapi: 3.0.1
    info:
      description: Manage all job requisitions
      title: Manage Job Requisition
      version: 1.0.14
      x-ibm-application-id: manage-job-requisitions-seq
      x-ibm-application-name: manage-job-requisitions-seq
      x-ibm-skill-type: imported
      x-ibm-application-icon: &lt;svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg\" viewBox="0 0 512 512"&gt;&lt;defs&gt;&lt;style&gt;.cls-1{fill:#fab922;}&lt;/style&gt;&lt;/defs&gt;&lt;path class="cls-1" d="M483,172.22c1.83,50.75-18.23,90.41-45.2,127C392.65,360.47,335,410.1,282.27,464.39c-4.5,4.64-9.19,9.09-13.75,13.67-13.43,13.5-13.64,13.09-26.73-.27C207.25,442.55,172,408.06,137.66,372.6c-36.26-37.5-71.22-75.85-94-124C14.56,187.18,27.35,109.72,76,65c43.11-39.64,113.8-47.54,166.5-17.66,10.23,5.79,17.22,5.74,27.52-.34,61.56-36.34,159.28-18.13,196.9,59.3C477.54,128.14,484,151,483,172.22Zm-403.76,7.7c1.38,25.13,10.54,49.49,25.08,71.92C139.81,306.58,188,350,233.75,395.41c7.07,7,12.77,18.89,22.54,19.14,10.11.26,15.13-12.33,22.12-19.34,38.76-38.86,79.38-76.09,112.6-120.06,27.85-36.84,49.26-75.65,37.21-125.07-9.71-39.83-44.34-69.2-85.37-69.88-31.29-.52-56.33,12.18-75.92,35.92-7.63,9.25-13.37,10.45-21.65.33C233.48,102.06,219,90.36,200.75,85,136.28,66.17,79.32,109.93,79.26,179.92Z"/&gt;&lt;/svg&gt;
    servers:
      - url: https://my_server.com
    paths:
      /getAllJobRequisitions:
        get:
          summary: All open job requisitions
          description: Get all Job Requisitions
          operationId: getJobRequisitions
          parameters:
            - required: false
              in: query
              name: location
              schema:
                type: string
            - required: false
              in: query
              name: jobProfile
              schema:
                type: string
            - name: status
              in: query
              description: Status of the JR
              required: true
              explode: true
              x-ibm-ui-extension:
                component: dropdown
                actions:
                  - skill_id: getter__1.0.0__status
                    type: data
                    params: {}
              schema:
                type: string
                enum:
                  - open
                  - closed
                default: open
          responses:
            '200':
              description: Returns all job requisitions.
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/JobRequistions'
    components:
      schemas:
        JobRequistions:
          type: object
          properties:
            instances:
              $ref: '#/components/schemas/job_requisitions'
        job_requisitions:
          type: array
          items:
            $ref: '#/components/schemas/Job_Requisition'
        Job_Requisition:
          type: object
          properties:
            jobReqId:
              type: string
              x-ibm-disable: 'true'
            jobStartDate:
              x-ibm-show: false
              type: string
            department:
              x-ibm-show: false
              type: string
            division:
              x-ibm-show: false
              type: string
            location:
              x-ibm-show: false
              title: Location
              type: string
            costOfHire:
              x-ibm-show: false
              type: string
            country:
              x-ibm-show: false
              type: string
            createdDateTime:
              x-ibm-show: false
              type: string
            currency:
              x-ibm-show: false
              type: string
            salRateType:
              x-ibm-show: false
              type: string
            salaryBase:
              x-ibm-show: false
              type: string
            candidateProgress:
              x-ibm-show: false
              type: string
            city:
              x-ibm-show: false
              type: string
            state:
              x-ibm-show: false
              type: string
            closedDateTime:
              x-ibm-show: false
              type: string
            jobDescription:
              title: Job Description
              x-ibm-multiline: true
              type: string
            hiringManager:
              title: Hiring Manager
              type: string
            jobProfile:
              title: Job Profile
              type: string
            workExperience:
              x-ibm-show: false
              type: string
            recruiter:
              title: Recruiter
              type: string
            competency_type:
              x-ibm-show: false
              type: string
            competency_description:
              type: string
            competency_id:
              x-ibm-show: false
              type: string
            status:
              x-ibm-show: false
              type: string
      securitySchemes:
        provider_basic_auth:
          type: http
          scheme: basic
    
  • {
      "openapi": "3.0.1",
      "info": {
        "description": "Manage all job requisitions",
        "title": "Manage Job Requisition",
        "version": "1.0.14",
        "x-ibm-application-id": "manage-job-requisitions-seq",
        "x-ibm-application-name": "manage-job-requisitions-seq",
        "x-ibm-skill-type": "imported",
        "x-ibm-application-icon": "&lt;svg id=\"Layer_2\" data-name=\"Layer 2\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"&gt;&lt;defs&gt;&lt;style&gt;.cls-1{fill:#fab922;}&lt;/style&gt;&lt;/defs&gt;&lt;path class=\"cls-1\" d=\"M483,172.22c1.83,50.75-18.23,90.41-45.2,127C392.65,360.47,335,410.1,282.27,464.39c-4.5,4.64-9.19,9.09-13.75,13.67-13.43,13.5-13.64,13.09-26.73-.27C207.25,442.55,172,408.06,137.66,372.6c-36.26-37.5-71.22-75.85-94-124C14.56,187.18,27.35,109.72,76,65c43.11-39.64,113.8-47.54,166.5-17.66,10.23,5.79,17.22,5.74,27.52-.34,61.56-36.34,159.28-18.13,196.9,59.3C477.54,128.14,484,151,483,172.22Zm-403.76,7.7c1.38,25.13,10.54,49.49,25.08,71.92C139.81,306.58,188,350,233.75,395.41c7.07,7,12.77,18.89,22.54,19.14,10.11.26,15.13-12.33,22.12-19.34,38.76-38.86,79.38-76.09,112.6-120.06,27.85-36.84,49.26-75.65,37.21-125.07-9.71-39.83-44.34-69.2-85.37-69.88-31.29-.52-56.33,12.18-75.92,35.92-7.63,9.25-13.37,10.45-21.65.33C233.48,102.06,219,90.36,200.75,85,136.28,66.17,79.32,109.93,79.26,179.92Z\"/&gt;&lt;/svg&gt;"
      },
      "servers": [
        {
          "url": "https://my_server.com"
        }
      ],
      "paths": {
        "/getAllJobRequisitions": {
          "get": {
            "summary": "All open job requisitions",
            "description": "Get all Job Requisitions",
            "operationId": "getJobRequisitions",
            "parameters": [
              {
                "required": false,
                "in": "query",
                "name": "location",
                "schema": {
                  "type": "string"
                }
              },
              {
                "required": false,
                "in": "query",
                "name": "jobProfile",
                "schema": {
                  "type": "string"
                }
              },
              {
                "name": "status",
                "in": "query",
                "description": "Status of the JR",
                "required": true,
                "explode": true,
                "x-ibm-ui-extension": {
                  "component": "dropdown",
                  "actions": [
                    {
                      "skill_id": "getter__1.0.0__status",
                      "type": "data",
                      "params": {}
                    }
                  ]
                },
                "schema": {
                  "type": "string",
                  "enum": [
                    "open",
                    "closed"
                  ],
                  "default": "open"
                }
              }
            ],
            "responses": {
              "200": {
                "description": "Returns all job requisitions.",
                "content": {
                  "application/json": {
                    "schema": {
                      "$ref": "#/components/schemas/JobRequistions"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "components": {
        "schemas": {
          "JobRequistions": {
            "type": "object",
            "properties": {
              "instances": {
                "$ref": "#/components/schemas/job_requisitions"
              }
            }
          },
          "job_requisitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job_Requisition"
            }
          },
          "Job_Requisition": {
            "type": "object",
            "properties": {
              "jobReqId": {
                "type": "string",
                "x-ibm-disable": "true"
              },
              "jobStartDate": {
                "x-ibm-show": false,
                "type": "string"
              },
              "department": {
                "x-ibm-show": false,
                "type": "string"
              },
              "division": {
                "x-ibm-show": false,
                "type": "string"
              },
              "location": {
                "x-ibm-show": false,
                "title": "Location",
                "type": "string"
              },
              "costOfHire": {
                "x-ibm-show": false,
                "type": "string"
              },
              "country": {
                "x-ibm-show": false,
                "type": "string"
              },
              "createdDateTime": {
                "x-ibm-show": false,
                "type": "string"
              },
              "currency": {
                "x-ibm-show": false,
                "type": "string"
              },
              "salRateType": {
                "x-ibm-show": false,
                "type": "string"
              },
              "salaryBase": {
                "x-ibm-show": false,
                "type": "string"
              },
              "candidateProgress": {
                "x-ibm-show": false,
                "type": "string"
              },
              "city": {
                "x-ibm-show": false,
                "type": "string"
              },
              "state": {
                "x-ibm-show": false,
                "type": "string"
              },
              "closedDateTime": {
                "x-ibm-show": false,
                "type": "string"
              },
              "jobDescription": {
                "title": "Job Description",
                "x-ibm-multiline": true,
                "type": "string"
              },
              "hiringManager": {
                "title": "Hiring Manager",
                "type": "string"
              },
              "jobProfile": {
                "title": "Job Profile",
                "type": "string"
              },
              "workExperience": {
                "x-ibm-show": false,
                "type": "string"
              },
              "recruiter": {
                "title": "Recruiter",
                "type": "string"
              },
              "competency_type": {
                "x-ibm-show": false,
                "type": "string"
              },
              "competency_description": {
                "type": "string"
              },
              "competency_id": {
                "x-ibm-show": false,
                "type": "string"
              },
              "status": {
                "x-ibm-show": false,
                "type": "string"
              }
            }
          }
        },
        "securitySchemes": {
          "provider_basic_auth": {
            "type": "http",
            "scheme": "basic"
          }
        }
      }
    }
    
Note:
  • The Manage Job Requisition app is unauthenticated. You can use any credentials and it works.
  • The skill in the Manage Job Requisition app doesn't display a dynamically generated list in the following cases:
    • If you do not add the Getter app and its skill to the skill set.
    • If it is not connected within the skill set.
    • If it does not return an array. In this case, the skill reverts to anything specified in the OpenAPI spec. In the example, you can see that it displays an enum with options, "Open" or "Closed".
  • The field enum under schema is optional. The values under enum are displayed when the server from where you fetch the data is down.

The following code is a working sample of the Getter app in YAML and JSON. Use this code to create an OpenAPI file and add this app to Watson Orchestrate:

  • openapi: 3.0.0
    info:
      x-ibm-application-name: getter
      x-ibm-application-id: getter
      x-ibm-skill-type: imported
      title: Getter
      description: Sample skill for a drop-down list.
      version: 1.0.0
    servers:
      - url: https://my_server.com
    paths:
      /getDropDownData:
        get:
          summary: Retrieve issue strings
          operationId: status
          responses:
            '200':
              description: A list of issue strings
              content:
                application/json:
                  schema:
                    type: array
                    items:
                      type: string
                  example:
                    - string1
                    - string2
                    - string3
    components:
      securitySchemes:
        provider_basic_auth:
          type: http
          scheme: basic
    
  • {
      "openapi": "3.0.0",
      "info": {
        "x-ibm-application-name": "getter",
        "x-ibm-application-id": "getter",
        "x-ibm-skill-type": "imported",
        "title": "Getter",
        "description": "Sample skill for a drop-down list.",
        "version": "1.0.0"
      },
      "servers": [
        {
          "url": "https://my_server.com"
        }
      ],
      "paths": {
        "/getDropDownData": {
          "get": {
            "summary": "Retrieve issue strings",
            "operationId": "status",
            "responses": {
              "200": {
                "description": "A list of issue strings",
                "content": {
                  "application/json": {
                    "schema": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "example": [
                      "string1",
                      "string2",
                      "string3"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "components": {
        "securitySchemes": {
          "provider_basic_auth": {
            "type": "http",
            "scheme": "basic"
          }
        }
      }
    }
    
Note:
  • The Getter app is unauthenticated. You can use any credentials and it works.
  • The current implementation expects the Getter app to return only an array of strings as a response.

Now, you can create and add your own skills into the skill catalog and include them in the skill set to use this functionality.

Skill output

Use the following options to manage the content of your skills output:

Understand the output form

The form that is presented to your user when a skill is completed is determined by the response schema defined by the OpenAPI specification for that skill. You can use the following extensions schema syntax to control how the output form is presented to your user.

All of these fields are supplied inside the OpenAPI schema:

  • 
    requestFeedback_output:
        type: object
        required:
        - addressee
        properties:
        addressee:
          type: string
          title: Addressee
          description: email address of the receiver/assignee
          x-ibm-show: true
        subject:
          type: string
          description: Subject to be reviewed
    
    
  • 
    {
       "requestFeedback_output": {
          "type": "object",
          "required": [
             "addressee"
          ],
          "properties": {
             "addressee": {
                "type": "string",
                "title": "Addressee",
                "description": "email address of the receiver/assignee",
                "x-ibm-show": "true"
             },
             "subject": {
                "type": "string",
                "description": "Subject to be reviewed",
    
             }
          }
       }
    }
    

title

The field title is standard syntax in an OpenAPI schema for supplying a human-readable name for a parameter. This title is displayed in the form in place of the field name:

  • 
        type: object
        properties:
        addressee:
          type: string
          title: Addressee
     
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "title": "Addressee"
          }
       }
    }
    

description

The field description is standard syntax in an OpenAPI schema for describing the use and purpose of a field. It is displayed to your user in the form:

  • 
        type: object
        properties:
        addressee:
          type: string
          description: Provide the addressee for this email
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "description": "Provide the addressee for this email"
          }
       }
    }
    

x-ibm-show

The extension x-ibm-show shows or hides output in the generated form. By default, all of the fields are shown:

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-show : true
     
  • 
    {
        "type": "object",
        "properties": {
        "addressee": {
          "type": "string",
          "x-ibm-show": "true"
        }
        }
    }
    
    

x-ibm-multiline

The extension x-ibm-multiline switches the text box used in the form for the input field from a single-line box to a multiline box.

  • 
        type: object
        properties:
        addressee:
          type: string
          x-ibm-multiline: "true"
    
  • 
    {
       "type": "object",
       "properties": {
          "addressee": {
             "type": "string",
             "x-ibm-multiline": "true"
          }
       }
    }
    

Skill output Figure 6: Understand the output form

Figure 6 illustrates how the use of extensions title, description, x-ibm-multiline and so on are reflected in the output form that is presented to your user.

Generate output tables

When the API returns a single array of objects, the output is presented as a table. Each item in the array is an entry in the table and the table headers are the fields inside the objects in that array.

Returning user-friendly errors

The skill set can call out an external service and that service can return an error. The details of the error must be returned to your user to help them understand what went wrong.

Writing effective error messages

Everyone can benefit from writing effective error messages that are clear and concise. The error messages you write must also clearly convey the essence of the error to the user without over-complicating the reason. A good error message communicates the root cause and also something that could lead your users to a resolution.

Following are some best practices while you write error messages:

  1. The message must convey the reason why the task did not complete. For example, "This pet doesn't exist".
  2. The message must be simple enough for your user to understand. For example, writing "The key doesn't exist in our database" requires an understanding of the software implementation that "This pet doesn't exist" does not.
  3. The error messages must not reveal details about the backend implementation of your system. For example, rather than returning a stack trace to your user, return as much relevant information as you can and the transaction ID so that it can be shared with technical support.
  4. It can be beneficial to use alternative errors when trying to protect your APIs. For example, a 404 error in place of a 403 error indicates that the API does not exist instead of revealing that your user doesn't have permission to access it. This might discourage attacks.

Returning a dynamic error message

To output a dynamic error message, return suitably structured JSON containing the details of the error:

  • 
    paths:
        /mypath:
        get: 
          responses:
            Error400:
              content:
              application/json:
                schema:
                $ref: "#/components/schemas/Errors"
            Error401:
              content:
              application/json:
                schema:
                $ref: "#/components/schemas/Errors"
            Error4XX:
              content:
              application/json:
                schema:
                $ref: "#/components/schemas/Errors"
            Error5XX:
              content:
              application/json:
                schema:
                $ref: "#/components/schemas/Errors"
    components:
        schemas:
        Errors:
          type: "object"
          description: "The Error object contains errors array that lists all of the errors that have occurred"
          required:
            - errors
          properties:
            errors:
              type: "array"
              items:
                $ref: "#/components/schemas/ErrorItem"
        ErrorItem:
          description: "Each error must have a code and a message"
          type: "object"
          required:
            - code
            - message
          additionalProperties: true,
          properties:
            code:
              description: "Error Code"
              type: "string"
            message:
              description: "Human readable message for this error"
              type: "string"
    
  • 
    	{
        "paths": {
        "/mypath": {
          "get": {
            "responses": {
              "Error400": {
                "content": null,
                "application/json": {
                  "schema": null,
                  "$ref": "#/components/schemas/Errors"
                }
              },
              "Error401": {
                "content": null,
                "application/json": {
                  "schema": null,
                  "$ref": "#/components/schemas/Errors"
                }
              },
              "Error4XX": {
                "content": null,
                "application/json": {
                  "schema": null,
                  "$ref": "#/components/schemas/Errors"
                }
              },
              "Error5XX": {
                "content": null,
                "application/json": {
                  "schema": null,
                  "$ref": "#/components/schemas/Errors"
                }
              }
            }
          }
        }
        },
        "components": {
        "schemas": {
          "Errors": {
            "type": "object",
            "description": "The Error object contains errors array that lists all of the errors that have occurred",
            "required": [
              "errors"
            ],
            "properties": {
              "errors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ErrorItem"
                }
              }
            }
          },
          "ErrorItem": {
            "description": "Each error must have a code and a message",
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": "true,",
            "properties": {
              "code": {
                "description": "Error Code",
                "type": "string"
              },
              "message": {
                "description": "Human readable message for this error",
                "type": "string"
              }
            }
          }
        }
        }
    }
    

In current scenario, as long as the object returned by the server matches the previously defined schema, the skill set can get the contents of the error objects and display the information to your user. You only need to include values for code and message in your ErrorItem field. You can also add more fields to help diagnosing the APIs outside of Watson Orchestrate.

Skill runtime

Sending additional headers to the API

Sometimes it is necessary to send additional data from Watson Orchestrate to the API through headers. For example, if the user's name and email are to be used by the API to do some additional authorization on the API's server.

The headers can be controlled at the info level, by providing the header key that can be used to pass the information.

  • 
    openapi: 3.0.0
    info:
        title: some-skill
        version: 1.0.0
        x-ibm-skill-headers:
        caller-id: "X-CUSTOM-HEADER-NAME-1"
        caller-name: "X-CUSTOM-HEADER-NAME-2"
    
  • 
    {
       "openapi": "3.0.0",
       "info": {
          "title": "some-skill",
          "version": "1.0.0",
          "x-ibm-skill-headers": {
             "caller-id": "X-CUSTOM-HEADER-NAME-1",
             "caller-name": "X-CUSTOM-HEADER-NAME-2"
          }
       }
    }
    
  • caller-id: Refers to the user's email address.

  • caller-name: Refers to the user's name (First name and last name).

It is sent with the API request in the X-CUSTOM-HEADER-NAME-1 and X-CUSTOM-HEADER-NAME-2 headers.

Sample OpenAPI specification

You can download the following sample OpenAPI specification file to know how the extensions are structured and defined in an OpenAPI: