NAV Navbar
json

Introduction

This website provides documentation for Twnel Chatbots Development by explaining a JSON definition.

Our goals are:

JSON Spec 1.0

chatbot flow spec

{
    "version": "1.0",
    "variables": {...},
    "messages": {...},
    "files": {...},
    "storage": {...},
    "entrypoint": "...",
    "transitions": {...},
    "actions": {...},
    "functions": {...}
}

The code to create a chatbot is a JSON file, as seen to the right. The spec, or JSON file, is based on a finite state machine model using transitions and actions. The actions include sending messages, calling APIs and executing functions.

The spec also include the resources needed for more complex functionality: variables, messages, templates, files and storage configurations to send data collected by chatbots to customers' infrastructures.

This is the web tool to define the JSON spec or visual representation.

Version

version

{
    "version": "1.0"
}

Indicates which version of the spec it is being used.

Variables (Chatbot, User, Session)

variables

{
    "user": {
        "phone": {
            "type": "string",
            "value": ""
        },
        "country": {
            "type": "string",
            "value": ""
        },
        "name": {
            "type": "string",
            "value": ""
        },
        "tags": {
            "type": "array",
            "value": []
        }
    },
    "chatbot": {
        "token": {
            "type": "string",
            "value": "token"
        }
    },
    "session": {
        "user_passcode": {
            "type": "integer",
            "value": 1
        }
    }
}

The framework will consider 3 variables types and chatbots will read or write to them based on the type

Types

JSON Types

6 Basic JSON types are considered: boolean, integer, number, string, array, object

Assignments will be allowed for session variables and operations will be immutable

Transitions

transitions

{
    "store_lottery_result": {
        "action": "store_lottery_result",
        "next": "get_lottery_message"
    },
    "get_lottery_message": {
        "action": "eval_next_for_passcode",
        "next": {
            "lose": "message_lose",
            "win": "message_win"
        }
    },
    "message_lose": {
        "action": "message_lose"
    },
    "message_win": {
        "action": "message_win"
    }
}

Each transition is a state. Each state has:

The next parameter can be fixed or can be calculated using session variables

Session variables hold the next state and on any transition values can be overwritten given logic

At the right, we can see how the store_lottery_result transition executes the store_lottery_result action and the next transition will be get_lottery_message

The next value can be conditional, using a dictionary. In transition get_lottery_message, if its action result is lose, the next transition will be message_lose; message_win otherwise, if the action result is win

Entrypoint

entrypoint

{
    "entrypoint": "ask_passcode"
}

Start transition to execute when the chatbot is triggered.

String value pointing to the transition. Any transition can be setup as the starting one

Storage

storage

{
    "customer": {
        "enable": false,
        "drivers": {
            "s3": {
                "bucket": "AWS_S3_BUCKET_NAME",
                "service_account": {
                    "name": "my_aws_s3_account",
                    "credentials": {
                        "aws_access_key_id": "",
                        "aws_secret_access_key": ""
                    }
                }
            },
            "azure": {
                "bucket": "AZURE_BLOB_CONTAINER_NAME",
                "service_account": {
                    "name": "my_azure_blob_account",
                    "credentials": {
                        "azure_storage_account_name": "",
                        "azure_storage_account_key": ""
                    }
                }
            },
            "gcloud": {
                "bucket": "GOOGLE_CLOUD_BUCKET_NAME",
                "service_account": {
                    "name": "my_google_storage_account",
                    "credentials": {
                        "type": "",
                        "project_id": "",
                        "private_key_id": "",
                        "private_key": "",
                        "client_email": "",
                        "client_id": "",
                        "auth_uri": "",
                        "token_uri": "",
                        "auth_provider_x509_cert_url": "",
                        "client_x509_cert_url": ""
                    }
                }
            }
        }
    },
    "twnel": {
        "enable": true,
        "mode": "secure"
    }
}

In this section we can define where files, collected by chatbots, will be stored.

2 modes of storage are provided:

Enable boolean parameters are added to turn on or off any storage option

In this example we can see the configurations for Amazon S3, Azure Blob Storage and Google Cloud Storage

Messages

messages

{
    "error_message": "An error has occurred: {{ error }}"
}

This option was included to define messages templates using jinja syntax, with placeholders.

In the future this list will be used for language translations, using i18n.

Names for messages are provided by the developer. We will see how this list of messages templates are used in chatbot actions.

Files

files

{
    "logo": "https://twnelassets.s3.amazonaws.com/twnel_logo.png",
    "my_audio": "AUDIO_S3_URL",
    "my_video": "VIDEO_S3_URL",
    "my_cert": "CERT_S3_URL",
    "my_pdf": "PDF_S3_URL"
}

Chatbot developer uploads files to be used and provides URLs.

Given Twnel media_url API functionality, the only thing that is needed for files are URLs.

Twnel reads files from Amazon S3 or Azure Storage and gets the right content type automatically

Functions

functions

{
    "get_passcode_next": {
        "runtime": "js",
        "version": "5",
        "code": "function get_passcode_next(input) { var code = input['code']; if (code == 'allow') { return 'win'; } else { return 'lose'; } }",
        "input": {
            "code": "string"
        },
        "output": {
            "type": "string",
            "values": [
                "win",
                "lose"
            ],
            "sample": "lose"
        }
    }
}

List of functions to be used. All functions receive an input object and returns an output

Parameters

Actions

send_message

send_message

{
    "type": "send_message",
    "vars": {},
    "messages": [
        {
            "template": "{{ messages.ask_passcode_message }}",
            "data": {}
        },
        "Second message"
    ],
    "input": {
        "type": "number",
        "method": "keyboard",
        "data": []
    },
    "media_url": "",
    "metadata": {
        "media": "image|audio|video|map|application/pdf",
        "some_data": "{ JSON string }",
    },
    "close_chat": true
}

Use to send a message with the right input type to the client and expect data. Files can be sent by giving the right media url as parameter.

Parameters

map_result

map_result

{
    "type": "map_result",
    "vars": {},
    "eval": {
        "targets": [
            {
                "from": "{{ transition.ask_passcode }}",
                "assign": [
                    {
                        "map": "val",
                        "to": "variables.session.user_passcode"
                    }
                ]
            }
        ]
    }
}

body example

{
    "k": "v",
    "a": [1, 2],
    "o": {
        "x": "2"
    }
}

action output

"output": {
    "answer": {
        "user": {...},
        "chatbot": {...},
        "session": {...}
    }
}

Define mapping data in order to read or write arbitrary JSON API responses and perform variables assignment. This is useful when building payloads to call APIs or to read API responses and perform assignments.

The mapper uses dot routes notation to read or write objects.

This is a powerful feature given one can read or write any value inside an API JSON response and assign the value to a variable, for further processing. Also, this action can be used to do transformations for variables used in functions or APIs.

At the rigth, we can see a body example. One can use the mapper with these routes:

The map_result actions uses object trees and reads or writes values using dot notation paths.

output

An output object will be created with the resulting variables as answer after applying all mappings

call_api

call_api

{
    "type": "call_api",
    "vars": {},
    "eval": {
        "method": "POST",
        "content_type": "application/json",
        "url": "https://api.production.twnel.io/users/random/numbers",
        "params": {},
        "headers": {
            "Authorization": "Bearer {{ variables.chatbot.token }}"
        },
        "body": "{ \"min\": {{ variables.chatbot.random_min }}, \"max\": {{ variables.chatbot.random_max }} }"
    },
    "post_eval": {
        "verify": {
            "status": [200, 201],
            "schema": {
                "type": "object",
                "properties": {
                    "number": {
                        "type": "number"
                    },
                    "input": {
                        "type": "object"
                    }
                }
            },
            "timecap": 1000
        }
    }
}

action output

"output": {
    "status": 200,
    "response_time": 250,
    "answer": ...
}

Define parameters to call HTTP endpoints. It supports all HTTP methods, query strings with params, headers and body payloads. The response will be added inside the transition as eval_response

Given the spec uses JSON and references values with JINJA syntax, in order to support integers or arrays inside the body, it is useful to use JSON string values and not body objects. If all attributes are strings, then the body object is a good option.

Status HTTP code will be added as a result in the transition as eval_response_data.status

This action also provides a way to verify the extected HTTP status code in the response. A list of expected codes can be provided. Also, in order to verify the response structure, we support json schema. The post_eval section can be seen to the right. Another verifier timecap is added to set the expected response time, in milliseconds, for the API call. If verifiers fail, error logs will be shown

If no post_eval section is specified for verifiers, 200 OK will be the default status code and the expected timecap or response time will be 2 seconds.

output

An output object will be created with the resulting HTTP status, response time in ms and API answer

call_function

call_function

{
    "type": "call_function",
    "vars": {},
    "eval": {
        "function": "evalutate_threshold",
        "input": {
            "number": "{{ variables.session.random_number }}",
            "threshold": "{{ variables.chatbot.threshold }}"
        }
    }
}

action output

"output": {
    "function": "func_name",
    "input": {...},
    "answer": ...
}

Use to execute functions, conditions for instance. Useful to calculate the next state in chatbot executions.

Given the functions specify contracts, or interfaces, the action just provides the values needed for execution.

We just need to reference the function to execute, evalutate_threshold in this example, and its input object. The response will be added inside the transition as eval_response

output

An output object will be created with the evaluated function name, input and answer

eval_next

eval_next

{
    "type": "eval_next",
    "vars": {},
    "eval": {
        "function": "get_passcode_next",
        "input": {
            "code": "{{ variables.session.passcode_result }}"
        }
    }
}

action output

"output": {
    "function": "func_name",
    "input": {...},
    "answer": "next_step"
}

There are times when we do not know what's going to be the next state because the current action is a random process and no input was given to the user. We need then to include an action to decide the next state using functions.

We use the same syntax as call_function, but eval_next signals developers that a conditional transition is taking place. The output of the function will be a string that matches the next dictionary conditionals

output

An output object will be created with the evaluated function name, input and answer

store_data

store_data

{
    "type": "store_data",
    "eval": {
        "driver": "s3",
        "uri": "{{ transition['capturar_foto']['output']['data'][1]['url'] }}"
    }
}

action output

"output": {
    "driver": "s3",
    "uri": "from_uri",
    "answer": "new_uri"
}

Used to store files in a storage service (s3, azure)

In this example a media will be copied to S3, using the storage setup, and the uri will be calculated by the framework. This new url can be used in other transitions, using call_api actions, to keep records up to date

output

An output object will be created with the driver used, input URI and final URI as answer

Input Types

When sending messages, one defines an input type or data we expect from the user. Here we describe the supported inputs and its parameters. Any chatbot will wait for the user input before resuming its execution.

text

text

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "text",
        "data": [
            { "placeholder": "..." }
        ]
    }
}

A simple text input will be requested.

A placeholder can be defined to be displayed in the input box and help the user

number

number

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "number",
        "data": [
            { "placeholder": "..." },
            { "integer": true/false },
            { "min": 1 },
            { "max": 200 }
        ]
    }
}

A number pad will be shown. If integer is true, dots in the keyboard will be disable.

A min and max parameters are used to define a valid range for the number

A placeholder can be defined to be displayed in the input box and help the user

phone

phone

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "phone",
        "data": [
            { "placeholder": "..." }
        ]
    }
}

Include a valid phone number using the E164 ISO format

A placeholder can be defined to be displayed in the input box and help the user

email

email

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "email",
        "data": [
            { "placeholder": "..." }
        ]
    }
}

Include a valid email using user@domain.com syntax

A placeholder can be defined to be displayed in the input box and help the user

radio

radio (spec v1)

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "radio",
        "data": [
            {"id": "yes", "label": "Acepto"},
            {"id": "no", "label": "No"}
        ]
    }
}

radio (spec v0)

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "radio",
        "data": [
            {"yes": "Acepto"},
            {"no": "No"}
        ]
    }
}

Perfect to allow the user to choose 1 option from a list. data includes available options. Keys are used to control the next transition and values will be displayed to the user.

In our example, Acepto and No will be the options the user can choose from. If Acepto is chosen, the yes will be given for the next transition

checkbox

checkbox (spec v1)

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "checkbox",
        "data": [
            {"id": "a", "label": "A"},
            {"id": "b", "label": "B"},
            {"id": "c", "label": "C"}
        ]
    }
}

checkbox (spec v0)

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "checkbox",
        "data": [
            {"a": "A"},
            {"b": "B"},
            {"c": "C"}
        ]
    }
}

Perfect to allow the user to choose multiple options from a list. data includes available options.

date

date

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "date",
        "data": [
            { "format": "AAAA-MM-DD"|"YYYY-mm-dd"|"dd/mm/YYYY"|"mm/dd/YYYY" },
            { "placeholder": "dd/mm/YYYY" },
            { "default" : { "day": 12, "month": 12, "year": 1988 } }
        ]
    }
}

Define dates. format follows ISO 8601 standard.

If no default date is provided, clients will not suggest a date. They will display the placeholder if any to help the user on writing the expected date. If default date is provided, and day, month or year is undefined, clients will use current time as default value

summary

Values for the default date can be either integers (12) or strings ("30")

password

password

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "password",
        "data": [
            { "subtype": "number|text" },
            { "size": 4|16 },
            { "confirm": true },
            { "confirm_message": "<CONFIRM_MESSAGE>" },
            { "fail_message": "<FAIL_MESSAGE>" },
            { "pub" : "[...]" }
        ]
    }
}

Allow the user to define a password, either numeric or alphanumeric, using the subtype parameter

A confirm and messages are provided to allow the user to define the password twice, following industry practices.

The pub is the rsa public key to be used to encrypt the password payload, making sure not even Twnel knows the secret and only the company can used the private key to decrypt the message.

location

location

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "location",
        "data": [
            { "subtype": "current"|"map" },
            { "format": "degrees"|"minutes"|"seconds" },
            { "timestamp": true/false },
            { "accuracy" : true/false }
        ]
    }
}

The user is shown a map and is asked to share a location. Using the subtype we have 2 modes:

If timestamp and accuracy are true, the gps parameters will be included in the response

media

media

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "media",
        "geotag": true/false,
        "data": [
            { "media_type": "image|audio|video|application/pdf" }
        ]
    }
}

Useful to ask media to the user: images, audios, videos etc. The functionality to geotag this media file is also available and the output will have the location parameters

barcode

barcode

{
    "type": "send_message",
    "input": {
        "method": "keyboard",
        "type": "barcode",
        "geotag": true/false,
        "data": [
            { "response": "RESPONSE_MESSAGE_AFTER_SCAN" }
        ]
    }
}

Useful to scan barcodes using the camera:

response indicates the output message body to send after the scan

The functionality to geotag this barcode is also available and the output will have the location parameters

Output Types

When receiving messages after input types, an output type is given inside the message info object. Here we describe the supported outputs and parameters. This is useful for further processing with functions or api calls

Use answer for processing or mappings

text_output

text

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "text",
            "data": [],
            "answer": {
                "value": "received text"
            }
        }
    }
}

Text in the message body or in answer object

number_output

number

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "number",
            "data": [],
            "answer": {
                "value": 7
            }
        }
    }
}

Number in the message body or in answer object

phone_output

phone

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "phone",
            "data": [],
            "answer": {
                "value": "+16170000102"
            }
        }
    }
}

Phone number in the message body or in answer object

email_output

email

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "email",
            "data": [],
            "answer": {
                "value": "me@mail.com"
            }
        }
    }
}

Email address in the message body or in answer object

radio_output

radio (spec v1)

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "radio",
            "data": [
                {"id": "yes", "label": "Acepto"}
            ],
            "answer": {
                "selected": {"id": "yes", "label": "Acepto"}
            }
        }
    }
}

radio (spec v0)

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "radio",
            "data": [
                {"yes": "Acepto"}
            ],
            "answer": {
                "selected": {"id": "yes", "label": "Acepto"}
            }
        }
    }
}

checkbox_output

checkbox (spec v1)

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "checkbox",
            "data": [
                {"id": "a", "label": "A"},
                {"id": "c", "label": "C"}
            ],
            "answer": {
                "selected": [
                    {"id": "a", "label": "A"},
                    {"id": "c", "label": "C"}
                ]
            }
        }
    }
}

checkbox (spec v0)

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "checkbox",
            "data": [
                {"a": "A"},
                {"c": "C"}
            ],
            "answer": {
                "selected": [
                    {"id": "a", "label": "A"},
                    {"id": "c", "label": "C"}
                ]
            }
        }
    }
}

Selected choice is given

date_output

date

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "date",
            "data": [
                { "format" : "dd/mm/YYYY|AAAA-MM-DD|mm/dd/YYYYTHH:mm:ssZ" },
                { "selected_date" : "11/09/2001|2001/09/11|09/11/1989T03:00:00Z" }
            ],
            "answer": {
                "format": "dd/mm/YYYY|AAAA-MM-DD|mm/dd/YYYYTHH:mm:ssZ",
                "selected_date" : "11/09/2001|2001/09/11|09/11/1989T03:00:00Z"
            }
        }
    }
}

Selected date is given. format follows ISO 8601 standard.

password_output

password

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "password",
            "data": [
                {"ciphered": "<CIPHERED_DATA>"}
            ],
            "answer": {
                "ciphered": "<CIPHERED_DATA>"
            }
        }
    }
}

The ciphered password will be included. In order to decrypt it, the private key must be used.

We recommend creating an endpoint that receives this ciphered password to decrypt it and do not use the private key as a bot variable

location_output

location

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "location",
            "data": [
                {"latitude": 42.32996014},
                {"longitude": -71.07001367},
                {"timestamp": 1562087482},
                {"accuracy": 20}
            ],
            "answer": {
                "latitude": 42.32996014,
                "longitude": -71.07001367,
                "timestamp": 1562087482,
                "accuracy": 20
            }
        }
    }
}

Geolocation parameters using degrees format.

timestamp in seconds and accuracy in meters. These last 2 may be -1 if no available.

media_output

media

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "media",
            "data": [
                {"media_type": "image|audio|video|application/pdf"},
                {"url": "FILE_URL"}
            ],
            "answer": {
                "media_type": "image|audio|video|application/pdf",
                "url": "FILE_URL"
            },
            "location": {
                "latitude": 42.32996014,
                "longitude": -71.07001367,
                "timestamp": 1562087482,
                "accuracy": 20
            }
        }
    }
}

Media URIs will be included with its media type. If geotag is true in input definition, geolocation parameters will also be included. -1 is the default value for any of the 4 location attributes

barcode_output

barcode

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "barcode",
            "data": [
                {"format": "BARCODE_FORMAT"},
                {"raw": "DECODED_TEXT"},
                {"type": "RESULT_TYPE"},
                {"value": "RESULT_VALUE"}
            ],
            "answer": {
                "format": "BARCODE_FORMAT",
                "raw": "DECODED_TEXT",
                "type": "RESULT_TYPE",
                "value": "RESULT_VALUE"
            },
            "location": {
                "latitude": 42.32996014,
                "longitude": -71.07001367,
                "timestamp": 1562087482,
                "accuracy": 20
            }
        }
    }
}

If geotag is true in input definition, geolocation parameters will also be included. -1 is the default value for any of the 4 location attributes

Barcode Formats

QR_CODE EAN_13 EAN_8 UPC_A UPC_E UPC_EAN_EXTENSION AZTEC CODABAR CODE_128 CODE_39 CODE_93 DATA_MATRIX ITF MAXICODE PDF_417 RSS_14 RSS_EXPANDED

Barcode Types

TEXT URI EMAIL_ADDRESS TEL GEO SMS ADDRESSBOOK CALENDAR ISBN PRODUCT VIN WIFI

Text Formatting

This small section describes how to format messages in Twnel

Reference

example

{
    "type": "send_message",
    "vars": {},
    "messages": [
        "text *text* _text_ ~text~ *_text_* *~text~* ~_text_~ *~_text_~*"
    ]
}
Format Code
Normal text
Bold *text*
Italic _text_
Strikethrough ~text~
Monospace ```text```

At the right we can see possible combinations

Examples

This section will provide examples with problems, specs and explanations that will allow developers to get familiar with the SDK and how it can be used to go from dream to chatbot as soon as possible

Sum Bot

{
  "version": "1.0",
  "variables": {
    "user": {
      "phone": {
        "type": "string",
        "value": ""
      },
      "country": {
        "type": "string",
        "value": ""
      },
      "name": {
        "type": "string",
        "value": ""
      },
      "tags": {
        "type": "array",
        "value": []
      }
    },
    "chatbot": {},
    "session": {
      "first_number": {
        "type": "number",
        "value": 0
      },
      "second_number": {
        "type": "number",
        "value": 0
      },
      "total_value": {
        "type": "number",
        "value": 0
      }
    }
  },
  "messages": {
    "intro_message": "Ok, let's start!",
    "first_number_message": "Insert your first number",
    "second_number_message": "Insert your second number",
    "total_message": "The result is: {{ total }}",
    "end_message": "Thank you for use me!"
  },
  "entrypoint": "start",
  "transitions": {
    "start": {
      "action": "start",
      "next": "ask_first_number"
    },
    "ask_first_number": {
      "action": "ask_first_number",
      "next": "map_first_number"
    },
    "map_first_number": {
      "action": "map_first_number",
      "next": "ask_second_number"
    },
    "ask_second_number": {
      "action": "ask_second_number",
      "next": "map_second_number"
    },
    "map_second_number": {
      "action": "map_second_number",
      "next": "make_sum"
    },
    "make_sum": {
      "action": "make_sum",
      "next": "map_sum_result"
    },
    "map_sum_result": {
      "action": "map_sum_result",
      "next": "send_total"
    },
    "send_total": {
      "action": "send_total",
      "next": "end"
    },
    "end": {
      "action": "end"
    }
  },
  "actions": {
    "start": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.intro_message }}",
          "data": {}
        }
      ]
    },
    "ask_first_number": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.first_number_message }}",
          "data": {}
        }
      ],
      "input": {
        "type": "number",
        "method": "keyboard",
        "data": []
      }
    },
    "map_first_number": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.ask_first_number.output }}",
            "assign": [
              {
                "map": "answer.value",
                "to": "variables.session.first_number"
              }
            ]
          }
        ]
      }
    },
    "ask_second_number": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.second_number_message }}",
          "data": {}
        }
      ],
      "input": {
        "type": "number",
        "method": "keyboard",
        "data": []
      }
    },
    "map_second_number": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.ask_second_number.output }}",
            "assign": [
              {
                "map": "answer.value",
                "to": "variables.session.second_number"
              }
            ]
          }
        ]
      }
    },
    "make_sum": {
      "type": "call_function",
      "vars": {},
      "eval": {
        "function": "sum_numbers",
        "input": {
          "first_number": "{{ variables.session.first_number }}",
          "second_number": "{{ variables.session.second_number }}"
        }
      }
    },
    "map_sum_result": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.make_sum.output }}",
            "assign": [
              {
                "map": "answer",
                "to": "variables.session.total_value"
              }
            ]
          }
        ]
      }
    },
    "send_total": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.total_message }}",
          "data": {
            "total": "{{ variables.session.total_value }}"
          }
        }
      ]
    },
    "end": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.end_message }}",
          "data": {}
        }
      ]
    }
  },
  "functions": {
    "sum_numbers": {
      "runtime": "js",
      "version": "5",
      "code": "function sum_numbers(input) { var total = 0; var first_number = input['first_number']; var second_number = input['second_number']; total = (first_number + second_number); return total; }",
      "input": {
        "first_number": "number",
        "second_number": "number"
      },
      "output": {
        "type": "number",
        "values": [],
        "sample": 0
      }
    }
  }
}

Simple bot that shows how to sum 2 numbers and get the result in a final message. Also shows how to:

Enable Passcode Game

chatbot_spec

{
  "version": "1.0",
  "variables": {
    "user": {
      "phone": {
        "type": "string",
        "value": ""
      },
      "country": {
        "type": "string",
        "value": ""
      },
      "name": {
        "type": "string",
        "value": ""
      },
      "tags": {
        "type": "array",
        "value": []
      }
    },
    "chatbot": {
      "token": {
        "type": "string",
        "value": "token"
      },
      "random_min": {
        "type": "integer",
        "value": 1
      },
      "random_max": {
        "type": "integer",
        "value": 100
      },
      "threshold": {
        "type": "integer",
        "value": 50
      }
    },
    "session": {
      "user_passcode": {
        "type": "integer",
        "value": 1
      },
      "random_number": {
        "type": "integer",
        "value": 1
      },
      "passcode_result": {
        "type": "string",
        "value": ""
      }
    }
  },
  "messages": {
    "error_message": "An error has occurred: {{ error }}",
    "ask_passcode_message": "Please enter a passcode number",
    "code_rejection_message": "We are sorry. Your input is ({{ number }}, {{ threshold }}) for code {{ code }} and result is: {{ result }}",
    "code_success_message": "Congratulations. Your input is ({{ number }}, {{ threshold }}) for code {{ code }} and result is: {{ result }}"
  },
  "files": {
    "logo": "https://twnelassets.s3.amazonaws.com/twnel_logo.png"
  },
  "storage": {
    "customer": {
      "enable": false,
      "drivers": {
        "s3": {
          "bucket": "twnel-storage-botcore",
          "service_account": {
            "credentials": {
              "aws_access_key_id": null,
              "aws_secret_access_key": null
            }
          }
        }
      }
    },
    "twnel": {
      "enable": true,
      "mode": "secure"
    }
  },
  "entrypoint": "ask_passcode",
  "transitions": {
    "ask_passcode": {
      "action": "ask_passcode",
      "next": "store_passcode"
    },
    "store_passcode": {
      "action": "store_passcode",
      "next": "get_random_number"
    },
    "get_random_number": {
      "action": "get_random_number",
      "next": "store_random_number"
    },
    "store_random_number": {
      "action": "store_random_number",
      "next": "exec_lottery_func"
    },
    "exec_lottery_func": {
      "action": "exec_lottery_func",
      "next": "store_lottery_result"
    },
    "store_lottery_result": {
      "action": "store_lottery_result",
      "next": "get_lottery_message"
    },
    "get_lottery_message": {
      "action": "eval_next_for_passcode",
      "next": {
        "lose": "message_lose",
        "win": "message_win"
      }
    },
    "message_lose": {
      "action": "message_lose"
    },
    "message_win": {
      "action": "message_win"
    }
  },
  "actions": {
    "ask_passcode": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.ask_passcode_message }}",
          "data": {}
        }
      ],
      "input": {
        "type": "number",
        "method": "keyboard",
        "data": [
          {
            "placeholder": "Please enter passcode"
          },
          {
            "integer": true
          }
        ]
      }
    },
    "store_passcode": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.ask_passcode.output }}",
            "assign": [
              {
                "map": "answer.value",
                "to": "variables.session.user_passcode"
              }
            ]
          }
        ]
      }
    },
    "get_random_number": {
      "type": "call_api",
      "vars": {},
      "eval": {
        "method": "POST",
        "content_type": "application/json",
        "url": "https://api.beta.twnel.me/users/random/numbers",
        "params": {},
        "headers": {
          "Authorization": "Bearer {{ variables.chatbot.token }}"
        },
        "body": "{ \"min\": {{ variables.chatbot.random_min }}, \"max\": {{ variables.chatbot.random_max }} }"
      },
      "post_eval": {
        "verify": {
          "status": [
            200,
            201
          ],
          "schema": {
            "type": "object",
            "properties": {
              "number": {
                "type": "number"
              },
              "input": {
                "type": "object"
              }
            }
          },
          "timecap": 1000
        }
      }
    },
    "store_random_number": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.get_random_number.output }}",
            "assign": [
              {
                "map": "answer.number",
                "to": "variables.session.random_number"
              }
            ]
          }
        ]
      }
    },
    "exec_lottery_func": {
      "type": "call_function",
      "vars": {},
      "eval": {
        "function": "evalutate_threshold",
        "input": {
          "number": "{{ variables.session.random_number }}",
          "threshold": "{{ variables.chatbot.threshold }}"
        }
      }
    },
    "store_lottery_result": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.exec_lottery_func.output }}",
            "assign": [
              {
                "map": "answer.result",
                "to": "variables.session.passcode_result"
              }
            ]
          }
        ]
      }
    },
    "eval_next_for_passcode": {
      "type": "eval_next",
      "vars": {},
      "eval": {
        "function": "get_passcode_next",
        "input": {
          "code": "{{ variables.session.passcode_result }}"
        }
      }
    },
    "message_lose": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.code_rejection_message }}",
          "data": {
            "number": "{{ variables.session.random_number }}",
            "threshold": "{{ variables.chatbot.threshold }}",
            "code": "{{ variables.session.user_passcode }}",
            "result": "{{ variables.session.passcode_result }}"
          }
        }
      ]
    },
    "message_win": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.code_success_message }}",
          "data": {
            "number": "{{ variables.session.random_number }}",
            "threshold": "{{ variables.chatbot.threshold }}",
            "code": "{{ variables.session.user_passcode }}",
            "result": "{{ variables.session.passcode_result }}"
          }
        }
      ],
      "close_chat": true
    }
  },
  "functions": {
    "evalutate_threshold": {
      "runtime": "js",
      "version": "5",
      "code": "function evalutate_threshold(input) { var output = {}; var number = input['number']; var threshold = input['threshold']; if (number < threshold) { output['result'] = 'reject'; } else { output['result'] = 'allow'; } return output; }",
      "input": {
        "number": "integer",
        "threshold": "integer"
      },
      "output": {
        "type": "object",
        "values": [],
        "sample": {
          "result": "reject"
        }
      }
    },
    "get_passcode_next": {
      "runtime": "js",
      "version": "5",
      "code": "function get_passcode_next(input) { var code = input['code']; if (code == 'allow') { return 'win'; } else { return 'lose'; } }",
      "input": {
        "code": "string"
      },
      "output": {
        "type": "string",
        "values": [
          "win",
          "lose"
        ],
        "sample": "lose"
      }
    }
  }
}

Simple game that shows how to use the spec and create a bot using APIs, Functions and Conditional Branching

Description

Bot asks user to enter a passcode number, number is used to call an API that returns a random number between 1 and 100. If random number is less than N, where N is a threshold the bot builder sets, bot will send a rejection message. If random number is greater or equal than N, the passcode will be authorized and a success message will be send.

Explanation

The main idea for this chatbot will be to ask the user a code, execute a random process and decide if the user wins the game. Given the randomness, the bot needs conditional branching to let the user know that happened with a success or error message.

To do this, we will use these actions:

And these resources:

Variables

User

Here Twnel provides always the phone number, country code and contact name, if any

Chatbot

Given these properties do not change for every user, they are constants or properties for the bot

Session

These variables change on every user interaction and will live on every user context

Transitions

The basic functionality is described with the next steps:

The functionality is a basic Finite State Machine and the next state is known most of the time.

The action eval_next at the end will execute the function get_passcode_next and given the result is a string, win or lose, the framework will know exactly where to go between these 2 transitions:

At the end the user will receive 1 of 2 possible messages:

API

The call_api action is used and the body is a JSON string in order to support sending a payload with numbers or integers, not only strings.

Also, this example show how to include a bearer token using headers and Authorization.

Functions

2 JavaScript functions are defined: evalutate_threshold and get_passcode_next. Each function defines its input object, with variables names and their types. An output object is used to set the type of the result and possible values.

Inputs and Media

chatbot_spec

{
  "version": "1.0",
  "variables": {
    "user": {
      "phone": {
        "type": "string",
        "value": ""
      },
      "country": {
        "type": "string",
        "value": ""
      },
      "name": {
        "type": "string",
        "value": ""
      },
      "tags": {
        "type": "array",
        "value": []
      }
    },
    "chatbot": {
      "pdf_name": {
        "type": "string",
        "value": "who.pdf"
      }
    },
    "session": {
      "location": {
        "type": "object",
        "value": {
          "latitude": 3.4982500076293945,
          "longitude": -76.47799682617188
        }
      },
      "choices": {
        "type": "object",
        "value": {
          "number": 0,
          "text": "text"
        }
      },
      "barcode": {
        "type": "object",
        "value": {
          "format": "",
          "raw": "",
          "type": "",
          "value": ""
        }
      },
      "geobarcode": {
        "type": "object",
        "value": {
          "latitude": -1,
          "longitude": -1,
          "timestamp": -1,
          "accuracy": -1
        }
      }
    }
  },
  "messages": {
    "ask_file_msg": "Please select the file you want",
    "checkbox_text": "You selected {{number}} and text is: {{text}}",
    "barcode_text": "Barcode Scan:\n\nformat: {{format}}\nraw: {{raw}}\ntype: {{type}}\nvalue: {{value}}",
    "barcode_geotext": "Barcode Scan:\n\nformat: {{format}}\nraw: {{raw}}\ntype: {{type}}\nvalue: {{value}}\n\nLocation: ({{lat}}, {{lng}}, {{tsp}}, {{acc}})",
    "media_geotext": "Media with Geotag:\n\nurl: {{url}}\n\nLocation: ({{lat}}, {{lng}}, {{tsp}}, {{acc}})"
  },
  "files": {
    "image": "https://twnelusermedia.s3-us-west-2.amazonaws.com/rpi.png",
    "audio": "https://twnelusermedia.s3-us-west-2.amazonaws.com/short.mp3",
    "video": "https://twnelusermedia.s3-us-west-2.amazonaws.com/space.mp4",
    "pdf": "https://www.minsalud.gov.co/sites/rid/Lists/BibliotecaDigital/RIDE/VS/PP/ET/abece-coronavirus.pdf"
  },
  "storage": {
    "twnel": {
      "enable": true,
      "mode": "public"
    }
  },
  "entrypoint": "media",
  "transitions": {
    "media": {
      "action": "media",
      "next": "message"
    },
    "geomedia": {
      "action": "geomedia",
      "next": "geomedia_msg"
    },
    "geomedia_msg": {
      "action": "geomedia_msg",
      "next": "message"
    },
    "message": {
      "action": "message"
    },
    "location": {
      "action": "location",
      "next": "message"
    },
    "map": {
      "action": "map",
      "next": "message"
    },
    "radio": {
      "action": "radio",
      "next": {
        "a": "message_a0",
        "b": "message_b0"
      }
    },
    "radio_v1": {
      "action": "radio_v1",
      "next": {
        "a": "message_a1",
        "b": "message_b1"
      }
    },
    "radio_media": {
      "action": "radio_media",
      "next": "message"
    },
    "message_a0": {
      "action": "message_a0",
      "next": "message"
    },
    "message_b0": {
      "action": "message_b0",
      "next": "message"
    },
    "message_a1": {
      "action": "message_a1",
      "next": "message"
    },
    "message_b1": {
      "action": "message_b1",
      "next": "message"
    },
    "password": {
      "action": "password",
      "next": "message"
    },
    "checkbox": {
      "action": "checkbox",
      "next": "checkbox_func"
    },
    "checkbox_func": {
      "action": "checkbox_func",
      "next": "checkbox_map"
    },
    "checkbox_map": {
      "action": "checkbox_map",
      "next": "checkbox_msg"
    },
    "checkbox_msg": {
      "action": "checkbox_msg",
      "next": "message"
    },
    "email": {
      "action": "email",
      "next": "message"
    },
    "phone": {
      "action": "phone",
      "next": "message"
    },
    "date": {
      "action": "date",
      "next": "message"
    },
    "date_str": {
      "action": "date_str",
      "next": "message"
    },
    "date_year": {
      "action": "date_year",
      "next": "message"
    },
    "number": {
      "action": "number",
      "next": "message"
    },
    "integer": {
      "action": "integer",
      "next": "message"
    },
    "tags": {
      "action": "tags"
    },
    "barcode": {
      "action": "barcode",
      "next": "barcode_map"
    },
    "barcode_map": {
      "action": "barcode_map",
      "next": "barcode_msg"
    },
    "barcode_msg": {
      "action": "barcode_msg",
      "next": "message"
    },
    "geobarcode": {
      "action": "geobarcode",
      "next": "geobarcode_map"
    },
    "geobarcode_map": {
      "action": "geobarcode_map",
      "next": "geobarcode_confirm"
    },
    "geobarcode_confirm": {
      "action": "geobarcode_confirm",
      "next": {
        "yes": "geobarcode_msg",
        "no": "geobarcode_msg"
      }
    },
    "geobarcode_msg": {
      "action": "geobarcode_msg",
      "next": "message"
    },
    "file": {
      "action": "file",
      "next": {
        "image": "image",
        "audio": "audio",
        "video": "video",
        "pdf": "pdf",
        "address": "address",
        "medias": "medias",
        "multimedia": "multimedia"
      }
    },
    "image": {
      "action": "image"
    },
    "audio": {
      "action": "audio"
    },
    "video": {
      "action": "video"
    },
    "pdf": {
      "action": "pdf"
    },
    "address": {
      "action": "address"
    },
    "medias": {
      "action": "image",
      "next": "audio"
    },
    "multimedia": {
      "action": "image",
      "next": "media"
    }
  },
  "actions": {
    "message": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "EOF"
      ]
    },
    "tags": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "{{ variables.user.tags | join(',') }}"
      ]
    },
    "message_a0": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "A: {{ transition.radio.output }}"
      ]
    },
    "message_b0": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "B: {{ transition.radio.output }}"
      ]
    },
    "message_a1": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "A: {{ transition.radio_v1.output }}"
      ]
    },
    "message_b1": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "B: {{ transition.radio_v1.output }}"
      ]
    },
    "number": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Number:"
      ],
      "input": {
        "method": "keyboard",
        "type": "number",
        "display": "vertical",
        "data": []
      }
    },
    "integer": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Integer:"
      ],
      "input": {
        "method": "keyboard",
        "type": "number",
        "display": "vertical",
        "data": [
          {
            "placeholder": "Integer in 1 and 200"
          },
          {
            "integer": true
          },
          {
            "min": 1
          },
          {
            "max": 200
          }
        ]
      }
    },
    "radio": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Radio:"
      ],
      "input": {
        "method": "keyboard",
        "type": "radio",
        "data": [
          {
            "a": "A"
          },
          {
            "b": "B"
          }
        ]
      }
    },
    "radio_v1": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Radio v1:"
      ],
      "input": {
        "method": "keyboard",
        "type": "radio",
        "data": [
          {
            "id": "a",
            "label": "A"
          },
          {
            "id": "b",
            "label": "B"
          }
        ]
      }
    },
    "password": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Password:"
      ],
      "input": {
        "type": "password",
        "method": "keyboard",
        "data": [
          {
            "subtype": "number"
          },
          {
            "size": 4
          },
          {
            "confirm": true
          },
          {
            "confirm_message": "puntoscolombia_ask_pin_confirm_message"
          },
          {
            "fail_message": "puntoscolombia_ask_pin_fail_message"
          },
          {
            "pub": "PUBKEY"
          }
        ]
      }
    },
    "checkbox": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Checkbox:"
      ],
      "input": {
        "method": "keyboard",
        "type": "checkbox",
        "display": "vertical",
        "data": [
          {
            "id": "a",
            "label": "Option A"
          },
          {
            "id": "b",
            "label": "Long Option B"
          },
          {
            "id": "c",
            "label": "Long Long Option C"
          },
          {
            "id": "d",
            "label": "Option D"
          },
          {
            "id": "e",
            "label": "Long Option E"
          }
        ]
      }
    },
    "checkbox_func": {
      "type": "call_function",
      "vars": {},
      "eval": {
        "function": "get_checkbox_text",
        "input": {
          "data": "{{ transition.checkbox.output.answer.selected }}"
        }
      }
    },
    "checkbox_map": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.checkbox_func.output }}",
            "assign": [
              {
                "map": "answer",
                "to": "variables.session.choices"
              }
            ]
          }
        ]
      }
    },
    "checkbox_msg": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.checkbox_text }}",
          "data": {
            "number": "{{ variables.session.choices.number }}",
            "text": "{{ variables.session.choices.text }}"
          }
        }
      ]
    },
    "email": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Email:"
      ],
      "input": {
        "method": "keyboard",
        "type": "email",
        "data": []
      }
    },
    "phone": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Phone:"
      ],
      "input": {
        "method": "keyboard",
        "type": "phone",
        "data": []
      }
    },
    "date": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Date (Integers):"
      ],
      "input": {
        "type": "date",
        "data": [
          {
            "format": "AAAA-MM-DD"
          },
          {
            "default": {
              "day": 4,
              "month": 7,
              "year": 2019
            }
          },
          {
            "placeholder": "AAAA-MM-DD"
          },
          {
            "default_day": "01"
          },
          {
            "default_month": "01"
          },
          {
            "default_year": "2000"
          }
        ]
      }
    },
    "date_str": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Date (Strings):"
      ],
      "input": {
        "type": "date",
        "data": [
          {
            "format": "AAAA-MM-DD"
          },
          {
            "default": {
              "day": "15",
              "month": "12",
              "year": "2018"
            }
          },
          {
            "placeholder": "AAAA-MM-DD"
          },
          {
            "default_day": "01"
          },
          {
            "default_month": "01"
          },
          {
            "default_year": "2000"
          }
        ]
      }
    },
    "date_year": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Date (Now):"
      ],
      "input": {
        "type": "date",
        "data": [
          {
            "format": "AAAA-MM-DD"
          },
          {
            "default": {}
          },
          {
            "placeholder": "AAAA-MM-DD"
          },
          {
            "default_day": "01"
          },
          {
            "default_month": "01"
          },
          {
            "default_year": "2000"
          }
        ]
      }
    },
    "location": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Current Location:"
      ],
      "input": {
        "type": "location",
        "method": "keyboard",
        "data": [
          {
            "subtype": "current"
          },
          {
            "format": "degrees"
          },
          {
            "timestamp": true
          },
          {
            "accuracy": true
          }
        ]
      }
    },
    "map": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Map Location:"
      ],
      "input": {
        "type": "location",
        "method": "keyboard",
        "data": [
          {
            "subtype": "map"
          },
          {
            "format": "degrees"
          },
          {
            "timestamp": true
          },
          {
            "accuracy": true
          }
        ]
      }
    },
    "media": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Media:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "data": [
          {
            "media_type": "image"
          }
        ]
      }
    },
    "geomedia": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Media with Location:"
      ],
      "input": {
        "type": "media",
        "geotag": true,
        "method": "keyboard",
        "data": [
          {
            "media_type": "image"
          }
        ]
      }
    },
    "geomedia_msg": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.media_geotext }}",
          "data": {
            "url": "{{ transition.geomedia.output.answer.url }}",
            "lat": "{{ transition.geomedia.output.location.latitude }}",
            "lng": "{{ transition.geomedia.output.location.longitude }}",
            "tsp": "{{ transition.geomedia.output.location.timestamp }}",
            "acc": "{{ transition.geomedia.output.location.accuracy }}"
          }
        }
      ]
    },
    "barcode": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Barcode:"
      ],
      "input": {
        "method": "keyboard",
        "type": "barcode",
        "display": "vertical",
        "data": [
          {
            "response": "Your Barcode was sent!"
          }
        ]
      }
    },
    "barcode_map": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.barcode }}",
            "assign": [
              {
                "map": "output.answer",
                "to": "variables.session.barcode"
              }
            ]
          }
        ]
      }
    },
    "barcode_msg": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.barcode_text }}",
          "data": {
            "format": "{{ variables.session.barcode.format }}",
            "raw": "{{ variables.session.barcode.raw }}",
            "type": "{{ variables.session.barcode.type }}",
            "value": "{{ variables.session.barcode.value }}"
          }
        }
      ]
    },
    "geobarcode": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Barcode with Location:"
      ],
      "input": {
        "method": "keyboard",
        "type": "barcode",
        "geotag": true,
        "display": "vertical",
        "data": [
          {
            "response": "Your Barcode was sent with geotagging!"
          }
        ]
      }
    },
    "geobarcode_map": {
      "type": "map_result",
      "vars": {},
      "eval": {
        "targets": [
          {
            "from": "{{ transition.geobarcode }}",
            "assign": [
              {
                "map": "output.answer",
                "to": "variables.session.barcode"
              },
              {
                "map": "output.location",
                "to": "variables.session.geobarcode"
              }
            ]
          }
        ]
      }
    },
    "geobarcode_confirm": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Is this your current location?"
      ],
      "input": {
        "method": "keyboard",
        "type": "radio",
        "display": "vertical",
        "data": [
          {
            "id": "yes",
            "label": "Yes"
          },
          {
            "id": "no",
            "label": "No"
          }
        ]
      },
      "metadata": {
        "media": "map",
        "data": "{ \"lat\": {{ variables.session.geobarcode.latitude }}, \"lng\": {{ variables.session.geobarcode.longitude }}, \"address\": \"\", \"name\": \"\" }"
      }
    },
    "geobarcode_msg": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.barcode_geotext }}",
          "data": {
            "format": "{{ variables.session.barcode.format }}",
            "raw": "{{ variables.session.barcode.raw }}",
            "type": "{{ variables.session.barcode.type }}",
            "value": "{{ variables.session.barcode.value }}",
            "lat": "{{ variables.session.geobarcode.latitude }}",
            "lng": "{{ variables.session.geobarcode.longitude }}",
            "tsp": "{{ variables.session.geobarcode.timestamp }}",
            "acc": "{{ variables.session.geobarcode.accuracy }}"
          }
        }
      ]
    },
    "file": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.ask_file_msg }}",
          "data": {}
        }
      ],
      "input": {
        "method": "keyboard",
        "type": "radio",
        "display": "vertical",
        "data": [
          {
            "image": "Image"
          },
          {
            "audio": "Audio"
          },
          {
            "video": "Video"
          },
          {
            "pdf": "PDF"
          },
          {
            "address": "Map"
          },
          {
            "medias": "Medias"
          },
          {
            "multimedia": "Multimedia"
          }
        ]
      }
    },
    "image": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "media_url": "{{ files.image }}",
      "metadata": {
        "media": "image"
      }
    },
    "audio": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "media_url": "{{ files.audio }}",
      "metadata": {
        "media": "audio"
      }
    },
    "video": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "media_url": "{{ files.video }}",
      "metadata": {
        "media": "video"
      }
    },
    "pdf": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "media_url": "{{ files.pdf }}",
      "metadata": {
        "media": "application/pdf"
      }
    },
    "address": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "metadata": {
        "media": "map",
        "data": "{ \"lat\": {{ variables.session.location.latitude }}, \"lng\": {{ variables.session.location.longitude }}, \"address\": \"\", \"name\": \"\" }"
      }
    },
    "radio_media": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Please select 1 option"
      ],
      "input": {
        "method": "keyboard",
        "type": "radio",
        "data": [
          {
            "id": "yes",
            "label": "Yes"
          },
          {
            "id": "no",
            "label": "No"
          }
        ]
      },
      "metadata": {
        "media": "map",
        "data": "{ \"lat\": {{ variables.session.location.latitude }}, \"lng\": {{ variables.session.location.longitude }}, \"address\": \"\", \"name\": \"\" }"
      }
    }
  },
  "functions": {
    "get_checkbox_text": {
      "runtime": "js",
      "version": "5",
      "code": "function get_checkbox_text(input) { var output = { 'number': 0, 'text': '' }; var data = []; if (input && ('data' in input) && Array.isArray(input['data'])) { data = input['data']; } var numChoices = data.length; if (numChoices) { output['number'] = numChoices; var choices = []; for (var i = 0; i < numChoices; i++) { if (('id' in data[i]) && ('label' in data[i])) { choices.push(data[i]['id'] + '-' + data[i]['label']); } } output['text'] = choices.join(','); } return output; }",
      "input": {
        "data": "array"
      },
      "output": {
        "type": "object",
        "values": [],
        "sample": {
          "number": 0,
          "text": "text"
        }
      }
    }
  }
}

Simple spec that shows how to use all input types, media files and basic radio usage.

Description

This is a simple bot that shows how to use all inputs described before. The functionality just consists of 2 steps:

Also, this spec shows how to make the bot even more interactive by sending:

Define all URLs at files section and inside send_message actions, set the metadata object as shown in order to let Twnel clients know how to display the information.

Platform APIs

When developing bots or integrating APIs for automation purposes, these APIs are useful:

send_one_message

send media message

curl -X POST \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/messages \
    -d '{"to": "PHONE_NUMBER", "body": "TEXT", "mediaUrl": "PUBLIC_MEDIA_URL" }'

Send a message to an user by specifying a valid E164 phone number, text and media url

put_contact

create or update contact

curl -X PUT \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/conversations/contacts \
    -d '{"phone": "PHONE_NUMBER", "name": "NAME", "tags": ["T1", "T2"] }'

This endpoint will normalize provided tags according to the Tag Spec (ASCII, Uppercase)

delete_contact

delete contact

curl -X DELETE \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/conversations/contacts/{PHONE}

Delete a contact by specifying the phone number

search_contact

search

curl -X GET \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/contacts/search\?q\=310

curl -X GET \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/contacts/search\?q\=andres

A q string parameter is provided to search by phone number or name

trigger_chatbot

trigger chatbot

curl -X PUT \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/messages/chatbots \
    -d '{ "chatbot_id": "CHATBOT_ID" "phone": "PHONE_NUMBER" }'

By providing a valid chatbot_id, this API activates the chatbot for a contact

deep link to start a b2c chat with a company

curl -X POST \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/companies/deeplinks \
    -d '{ "action": "start_chat", "message": "STARTING_MESSAGE", "send": true/false,
"phone": "PHONE_NUMBER" }'

Full integration support with third-party applications through deep-linking. Useful for opening conversations and sending messages from other apps. This service extends deep link functionality for installation and redirects a user to a company chatroom with a starting message. A flag is included to control whether to just fill the text field or also send the message automatically.

B2C Deep Link Example

{
    "link": “https://twnel.link/pGWcKnUkeGqSoMPEA"
}

Mobile Client

The client will use the deep link to send the message to the company on behalf of the user. If the phone parameter arrives, the client checks if there is a match between the deep link phone parameter and the account owner. if send parameter is “false“, client will fill the text field; if “true“, it will fill the text field and send the message automatically.