NAV
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.

Transitions

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": {
        "session_id": {
            "type": "string",
            "value": ""
        },
        "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"
        }
    }
}

{
    "check_customer": {
        "runtime": "jsonlogic",
        "version": "",
        "code": {
            "conditions": [
                {
                    "label": "vip customer",
                    "rule": {
                        "and": [
                            {">=": [{"var": "age"}, 21]},
                            {"==": [{"var": "vip"}, "yes"]}
                        ]
                    },
                    "output": "c1"
                },
                {
                    "label": "child user",
                    "rule": {
                        "and": [
                            {"<=": [{"var": "age"}, 18]},
                            {"==": [{"var": "school"}, "yes"]}
                        ]
                    },
                    "output": "c2"
                }
            ]
        },
        "input": {
            "age": "integer",
            "vip": "string",
            "school": "string"
        },
        "output": {
            "type": "string",
            "values": [
                "c1",
                "c2",
                "default"
            ],
            "sample": "c1"
        }
    }
}

List of functions to be used. All functions receive an input object and return an output. The programmer can define functions in JavaScript ES5 for complex functionality or JSON Logic for simple conditions based on rules.

For conditions, the developer defines a list of conditions and they are evaluated in order. If condition is true, its output will be the result of the function. If no condition is met, default is returned. To see the documentation for JSON Logic, go to json logic website

Parameters

Variable Types

Actions

send_message

send_message

{
    "type": "send_message",
    "vars": {},
    "messages": [
        {
            "template": "{{ messages.ask_passcode_message }}",
            "data": {}
        },
        "Second message"
    ],
    "input": {
        "type": "number",
        "method": "keyboard",
        "timeout": 3,
        "data": []
    },
    "media_url": "",
    "metadata": {
        "media": "image|audio|video|map|application/pdf"
    },
    "delay": 3,
    "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 | application/x-www-form-urlencoded | text/xml",
        "url": "https://api.production.twnel.io/users/random/numbers",
        "timeout": 5000,
        "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
        }
    },
    "on_error": {
        "next": "message_api_error"
    }
}
"on_error": {
    "next": {
        "eval": "message_api_error_eval",
        "timeout": "message_api_error_timeout",
        "status": "message_api_error_status",
        "schema": "message_api_error_schema",
        "default": "message_api_error_default"
    }
}

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 or in the output

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

A timeout parameter has been added to specify the time to wait in milliseconds before triggering an error. Max timeout is 27.5 seconds.

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.

A new section named on_error has been added to set the next transition to execute in case of any error. If the verify section in post_eval is defined and an unexpected response is received, this is considered an error. If no on_error is defined, the bot will executed the next action defined in the transitions section as expected.

Content Types:

Types of Errors and Priorities:

on_error can also be used to define conditional transitions based on the specific error and 1 transition will be executed given this order if there are multiple errors:

The timecap does not trigger errors and must be used for debugging, logging or measuring.

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 }}"
        }
    },
    "on_error": {
        "next": "message_func_error"
    }
}

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

A new section named on_error has been added to set the next transition to execute in case of any error. If no on_error is defined, the bot will executed the next action defined in the transitions section as expected.

output

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

conditional_step

conditional_step

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

conditional transitions

{
    "action": "conditional_step_action",
    "next": {
        "lose": "func_message_lose",
        "win": "func_message_win"
    }
}

action output

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

There are times when we need to execute an action given a condition. We have no idea which transition is going to be executed. For these cases, we use this action to set the next state dinamically. For instance, if my client is VIP, we want to execute transitions for VIP customers; otherwise, we can execute other actions. In order to define the next transition, we define a function that is going to be executed and will return the string of the key for the next transition or action to be executed

In this example, if the function returns lose, the func_message_lose will be executed. if the function returns win, the func_message_win will be executed.

We use the same syntax as call_function, but conditional_step 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_file

store_file

{
    "type": "store_file",
    "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 (ie: s3)

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. The access policy of the new file will be public if the bucket is public.

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" },
            { "picker": "camera/gallery/all" },
            { "facing": "back/front" }
        ]
    }
}

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.

It is also possible to set the picker to choose the media from. Both camera and gallery are supported. When using the camera, its facing can be set for a selfie or normal photo.

barcode

barcode

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

Useful to scan barcodes using the camera:

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

subtype specifies the kind of barcode that is expected and the application setups the scanner accordingly. If subtype is not defined, is empty or value is not supported, the client setups the scanner for autodetection. Supported values:

more values could be added over time to setup the scanner

Response

response indicates the output message body to send after the scan

signature

signature

{
    "type": "send_message",
    "input": {
        "type": "signature",
        "method": "keyboard",
        "geotag": true/false,
        "data": [
            { "caption": "SIGNATURE_CAPTION_TEXT" }
        ]
    }
}

Mobile client shows a canvas and lets the user draw a signature. Once completed, the client creates and sends an image. caption indicates the text to show below the signature box. If not provided, these texts will be displayed:

ES: Firma aquí EN: Sign here PR: Assine aqui

Caption Rules

Location

The functionality to geotag this signature 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"},
                "id": "yes",
                "label": "Acepto"
            }
        }
    }
}

radio (spec v0)

{
    "info": {
        "output": {
            "version": "1.0",
            "type": "radio",
            "data": [
                {"yes": "Acepto"}
            ],
            "answer": {
                "selected": {"id": "yes", "label": "Acepto"},
                "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"}
                ],
                "ids": ["a", "c"],
                "labels": ["A", "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"}
                ],
                "ids": ["a", "c"],
                "labels": ["A", "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. Default values if not present in the response:

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

timestamp in seconds and accuracy in meters. Default values if not present in the response:

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

timestamp in seconds and accuracy in meters. Default values if not present in the response:

signature_output

signature

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

url will contain the image with the provided signature

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

timestamp in seconds and accuracy in meters. Default values if not present in the response:

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

Filters

It is possible to transform the data processed in the strings used within the bot spec whenever the syntax with double brackets ({{ var }}) is available.

The data transformation can be achieved using any of the Jinja builtin filters. Please check the official Jinja documentation for further information.

Besides the builtin Jinja filters, there are additional custom filters implemented which are shown in the samples bellow.

Reference

example

{
    "type": "send_message",
    "vars": {
        "empty_array": [],
        "currency": 2019,
        "url": "http://example.com",
        "json_obj": {},
        "json_str": "{}"
    },
    "messages": [
        "{{ vars.empty_array | length | ternary(true, false) }}"
        "{{ vars.currency | currency('en_US.UTF-8') }}"
        "{{ vars.url | urlencode }}"
        "{{ vars.json_str | to_json_obj }}"
        "{{ vars.json_obj | to_json_str }}"
    ]
}
Filter Description Output (sample)
ternary Evaluates an expression and returns either of the parameters depending on its falsiness false
currency Converts a quantity into a given currency format $2,019.00
urlencode Makes a string URL safe http%3A%2F%2Fexample.com
json_obj Returns JSON object from a string {}
json_str Returns JSON string from an object '{}'

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 your ticket 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"
    },
    "message_api_error": {
      "action": "message_api_error"
    },
    "message_api_error_status": {
      "action": "message_api_error_status"
    },
    "message_func_error": {
      "action": "message_func_error"
    }
  },
  "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",
        "timeout": 5000,
        "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": 3000
        }
      },
      "on_error": {
        "next": {
          "default": "message_api_error",
          "status": "message_api_error_status"
        }
      }
    },
    "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_new": {
      "type": "call_function",
      "vars": {},
      "eval": {
        "function": "wrong_json",
        "input": {
          "code": "{{ variables.session.random_number }}"
        }
      },
      "on_error": {
        "next": "message_func_error"
      }
    },
    "exec_lottery_func": {
      "type": "call_function",
      "vars": {},
      "eval": {
        "function": "evalutate_threshold",
        "input": {
          "number": "{{ variables.session.random_number }}",
          "threshold": "{{ variables.chatbot.threshold }}"
        }
      },
      "on_error": {
        "next": "message_func_error"
      }
    },
    "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
    },
    "message_api_error": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.error_message }}",
          "data": {
            "error": "API call did not succeed. Please try again later"
          }
        }
      ],
      "close_chat": true
    },
    "message_api_error_status": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.error_message }}",
          "data": {
            "error": "API call did not succeed given a status"
          }
        }
      ],
      "close_chat": true
    },
    "message_func_error": {
      "type": "send_message",
      "vars": {},
      "messages": [
        {
          "template": "{{ messages.error_message }}",
          "data": {
            "error": "Function call did not succeed. Please try again later"
          }
        }
      ],
      "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"
      }
    },
    "wrong_json": {
      "runtime": "js",
      "version": "5",
      "code": "function wrong_json(input) { return JSON.stringify(xyz) }",
      "input": {
        "code": "string"
      },
      "output": {
        "type": "string",
        "values": [],
        "sample": "anything"
      }
    }
  }
}

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": "secure"
    }
  },
  "entrypoint": "media",
  "transitions": {
    "media": {
      "action": "media",
      "next": "message"
    },
    "gallery": {
      "action": "gallery",
      "next": "message"
    },
    "camera": {
      "action": "camera",
      "next": "message"
    },
    "camera_all": {
      "action": "camera_all",
      "next": "message"
    },
    "camera_front": {
      "action": "camera_front",
      "next": "message"
    },
    "camera_back": {
      "action": "camera_back",
      "next": "message"
    },
    "geomedia": {
      "action": "geomedia",
      "next": "geomedia_msg"
    },
    "geomedia_msg": {
      "action": "geomedia_msg",
      "next": "message"
    },
    "signature": {
      "action": "signature",
      "next": "signature_message"
    },
    "signature_message": {
      "action": "signature_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",
        "delays": "msg_with_delay_one"
      }
    },
    "image": {
      "action": "image"
    },
    "audio": {
      "action": "audio"
    },
    "video": {
      "action": "video"
    },
    "pdf": {
      "action": "pdf"
    },
    "address": {
      "action": "address"
    },
    "medias": {
      "action": "image",
      "next": "audio"
    },
    "multimedia": {
      "action": "image",
      "next": "radio_v1"
    },
    "msg_with_delay_one": {
      "action": "msg_with_delay_one",
      "next": "msg_with_delay_two"
    },
    "msg_with_delay_two": {
      "action": "msg_with_delay_two",
      "next": "message"
    }
  },
  "actions": {
    "message": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "EOF"
      ]
    },
    "signature_message": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "{{ transition.signature.output.answer.url | safe }}"
      ]
    },
    "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": [
        "{{ transition.checkbox.output }}",
        {
          "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": "dd/mm/YYYY"
          },
          {
            "placeholder": "dd/mm/YYYY"
          }
        ]
      }
    },
    "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"
          }
        ]
      }
    },
    "gallery": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Gallery:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "geotag": false,
        "data": [
          {
            "media_type": "image"
          },
          {
            "picker": "gallery"
          }
        ]
      }
    },
    "camera": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Camera:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "geotag": false,
        "data": [
          {
            "media_type": "image"
          }
        ]
      }
    },
    "camera_all": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Camera All:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "geotag": false,
        "data": [
          {
            "media_type": "image"
          },
          {
            "picker": "all"
          }
        ]
      }
    },
    "camera_front": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Camera Front:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "geotag": false,
        "data": [
          {
            "media_type": "image"
          },
          {
            "picker": "camera"
          },
          {
            "facing": "front"
          }
        ]
      }
    },
    "camera_back": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Camera Back:"
      ],
      "input": {
        "type": "media",
        "method": "keyboard",
        "geotag": false,
        "data": [
          {
            "media_type": "image"
          },
          {
            "picker": "camera"
          },
          {
            "facing": "back"
          }
        ]
      }
    },
    "signature": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Signature:"
      ],
      "input": {
        "type": "signature",
        "method": "keyboard",
        "geotag": true,
        "data": []
      }
    },
    "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"
          },
          {
            "delays": "Delays"
          }
        ]
      }
    },
    "image": {
      "type": "send_message",
      "vars": {
        "chosen": "{{ ['https://twnelusermedia.s3-us-west-2.amazonaws.com/rpi.png','https://images.barcodelookup.com/1073/10737859-1.jpg','https://i.pinimg.com/originals/90/d8/cf/90d8cf5d0b5ede7751fb0d4f30147ee9.png'] | random }}"
      },
      "messages": [
        "{{ vars.chosen }}"
      ],
      "media_url": "{{ vars.chosen }}",
      "metadata": {
        "media": "image"
      },
      "delay": 1
    },
    "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",
        "filename": "some name"
      }
    },
    "address_raw": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "metadata": {
        "media": "map",
        "data": {
          "lat": 3.4982500076293945,
          "lng": -76.47799682617188,
          "address": "Street A XYZ ",
          "name": "Some Building"
        }
      }
    },
    "address": {
      "type": "send_message",
      "vars": {},
      "messages": [
        ""
      ],
      "metadata": {
        "media": "map",
        "data": "{ \"lat\": {{ variables.session.location.latitude }}, \"lng\": {{ variables.session.location.longitude }}, \"address\": \"Street A XYZ \", \"name\": \"Some Building\" }"
      }
    },
    "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\": \"\" }"
      }
    },
    "msg_with_delay_one": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Message and delay 1"
      ],
      "delay": 3
    },
    "msg_with_delay_two": {
      "type": "send_message",
      "vars": {},
      "messages": [
        "Message and delay 2"
      ],
      "delay": 4
    }
  },
  "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

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

A q string parameter is provided to search by phone number or name. To search based on tags, add a new tags query string with a comma-separated value as shown.

list_superbots

list available superbots

curl -X GET \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer TOKEN" \
    https://api.production.twnel.com/companies/superbots

Returns active superbots indicating both the chatbot id and name. The id is useful in order to trigger a bot using the trigger_chatbot endpoint

trigger_chatbot

trigger chatbot

curl -X POST \
    -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.

Integration APIs

Retrieve Records from Google Spreadsheet

Get Record Example

{
  "spreadsheetId": "XXX",
  "userProfileId": "YYY",
  "range": "'Sheet1'!A1:Z",
  "labels": [
    "nombre",
    "celular"
  ],
  "query": {
    "celular": "ZZZ",
    "nombre": ["[!]WWW"]
  },
  "returnOnly": "all"
}

https://apis.twnel.io/google/get_records

Method POST

Description Retrieves records from a specified Google Spreadsheet based on the provided query parameters. It supports filtering by multiple criteria and can return all matches, the first match, or the last match.

Parameters

Notes

Create Records in Google Spreadsheet

Create Record Example

{
  "spreadsheetId": "XXX",
  "userProfileId": "YYY",
  "range": "'Sheet1'!A1:Z",
  "labels": [
    "documento",
    "date"
  ],
  "data": [
    {
      "documento": "=HYPERLINK(\"google.com\", \"hyperlink\")"
    }
  ],
  "addDate": true,
  "addDateLabel": "date",
  "locales": "es-CO",
  "formatDate": {
    "timeZone": "America/Bogota"
  }
}

https://apis.twnel.io/google/create_records

Method POST

Description Allows for the creation of new records in a specified Google Spreadsheet. It supports the insertion of data including Google Sheets formulas and the automatic addition of timestamps.

Parameters

Notes

Update Records in Google Spreadsheet

Update Record Example

{
  "spreadsheetId": "XXX",
  "userProfileId": "YYY",
  "range": "'Sheet1'!A1:Z",
  "labels": [
    "documento"
  ],
  "query": {
    "documento": "5"
  },
  "returnOnly": "lastMatch",
  "update": {
    "documento": "50"
  }
}

https://apis.twnel.io/google/update_records

Method PATCH

Description Updates existing records in a specified Google Spreadsheet based on provided query parameters. It supports partial updates, where only specified fields are changed, and other fields retain their existing values.

Parameters

For other parameters, see the section on the creation of records

Notes

Delete Records in Google Spreadsheet

Delete Record Example

{
  "spreadsheetId": "XXX",
  "userProfileId": "YYY",
  "range": "'Sheet1'!A1:G",
  "labels": [
    "documento"
  ],
  "query": {
    "documento": 1352
  }
}

https://apis.twnel.io/google/delete_records

Method DELETE

Description Deletes records from a specified Google Spreadsheet based on provided query parameters.

Parameters

See the section on obtaining records

Notes

Create PDF from Google Docs Template

Create PDF Example

{
  "userProfileId": "XXX",
  "templateId": "YYY",
  "folderId": "ZZZ",
  "requests": [
    {
      "type": "text",
      "value": "REPLACED TEXT",
      "match": "<<TEXT>>"
    },
    {
      "type": "text",
      "value": "BOLD TEXT",
      "options": {
        "bold": true,
        "alignment": "CENTER",
        "fontSize": {
          "magnitude": 16,
          "unit": "PT"
        }
      }
    },
    {
      "type": "image",
      "value": "URL",
      "options": {
        "alignment": "CENTER"
      }
    },
    {
      "type": "table",
      "value": [
        ["A", "B", "C"],
        ["1", "2", "3"]
      ]
    }
  ]
}

https://apis.twnel.io/google/create_pdf

Method POST

Description Creates a PDF from a specified Google Docs template, allowing for dynamic insertion and replacement of text, images, and tables. It supports text styling and paragraph formatting.

Parameters

Request Types

  1. text: Inserts or replaces text in the template.

    • value (string): The new text value to be inserted or used for replacement.
    • match (string, optional): The placeholder in the template to be replaced. If provided, this text will replace the matching placeholder.
    • options (object, optional): Paragraph and text style options for the text.
  2. image: Inserts or replaces an image in the template.

    • value (string): The URL of the image to be inserted.
    • match (boolean, optional): If set to true, replaces the first image in the template, from top to bottom, left to right.
    • options (object, optional): Paragraph style options for the image.
  3. table: Inserts a table in the template.

    • value (array of arrays): The matrix of the table data to be inserted.
    • options (object, optional): Paragraph style options for the table.

Notes

OCR Invoice Processing

OCR Invoice Example

{
  "userProfileId": "XXX",
  "url": "URL",
  "schema": {
    "type": "object",
    "properties": {
      "documento": {
        "type": "string",
        "description": ""
      }
    }
  }
}

https://apis.twnel.io/google/invoice_ocr

Method POST

Description Processes an invoice image using Optical Character Recognition (OCR) and extracts data according to a specified JSON Schema.

Parameters

Notes

Send Email

Send Email Example

{
  "userProfileId": "XXX",
  "text": "<p>Hi, world!</p> <img src=\"cid:image_example\"/>",
  "to": ["example@domain.com"],
  "subject": "Test",
  "attachments": [
    {
      "path": "URL",
      "contentType": "image/png",
      "name": "image_example.png"
    }
  ]
}

https://apis.twnel.io/google/send_email

Method POST

Description Sends an email with the specified content, recipients, subject, and attachments.

Parameters

Notes