Bixby Developer Center

Guides

Configuring Endpoints

Bixby supports the following ways to implement actions:

  • Local endpoint: A JavaScript implementation of your action which accepts input concepts as JSON objects and returns a JSON object as output. A local endpoint is a JavaScript file included in your capsule code, executed in the Bixby platform's JavaScript environment.
  • Remote endpoint: A web service that implements your action. This service also accepts input concepts as JSON objects and returns a JSON object as output, but it runs on a publicly-accessible web server that you control.

There are advantages and disadvantages when using remote endpoints over local endpoints:

Advantages

  • You can leverage an existing web service.
  • You can implement your actions in any language that runs on your web server, rather than being restricted to JavaScript and the specific libraries we support.

Disadvantages

  • During development, you must expose a public web server running your development code. The Bixby platform does not have access to your local addresses and hostnames like localhost (see the Localhost Tunneling section for details on how you can achieve this).
  • Your remote web server must return results and/or accept parameters as JSON objects compatible with the structure of Bixby concepts, just like local endpoint implementations.
  • You are responsible for the maintenance and uptime of your codebase and server.

You create action endpoints (both remote and local) through an endpoints.bxb file that defines the action, including the inputs the action will accept and the endpoints that the action will call. You can have multiple endpoints.bxb files to support different types of endpoints, but they must be placed within your capsule resources folder.

  • /resources/base/: endpoints that apply to all locales
  • /resources/base/%locale%/: endpoints that apply to a specific locale, such as en-us.

Endpoint definitions follow a similar format for all endpoint types.

endpoints {
action-endpoints {
action-endpoint (FindShoe) {
accepted-inputs ()
local-endpoint (FindShoe.js)
}
action-endpoint (FindShoeFiltering) {
accepted-inputs (type)
local-endpoint (FindShoeFiltering.js)
}
action-endpoint (FindShoeRemoteEndpoint) {
accepted-inputs ()
remote-endpoint ("{remote.url}/shoes") {
method (GET)
}
}
}
}
  • The action-endpoint key defines and names a specific endpoint, such as FindShoe.
  • The local-endpoint key specifies the name of the JavaScript file that implements the action, located in the capsule's code/ folder.
  • The remote-endpoint key specifies the web services URL to call.
    • It can include a configuration property defined in the capsule.properties file.
    • It must specify its HTTP method, usually GET or POST.
  • The accepted-inputs key defines the parameters that are passed to the action.
    • This key can be empty if the action has no parameters.
    • If this line is not given, Bixby will use the inputs defined in the action model.
    • For a local endpoint, the parameter names in the JavaScript function must match the names given in accepted-inputs (or the input keys in the action model).
    • For a remote endpoint, these input names will become the property names in the JSON object passed as a parameter.
    • The $vivContext parameter may be included. This will allow your actions to access user-specific information.

Local Endpoints

For local endpoints, you must declare the local file (local-endpoint) that contains the external action implementation as well the inputs to accept (accepted-inputs). As with the JavaScript that you use within capsule, you can use $vivContext to access user-specific information. The example below shows three ways local endpoints can be specified: with and without $vivContext, and in the third example, calling a specific function, getSock(), within the FindSock.js file.

endpoints {
action-endpoints {

// simple local-endpoint
action-endpoint (FindShoe) {
accepted-inputs (name, type, minPrice, maxPrice)
local-endpoint ("FindShoe.js")
}

// simple local-endpoint with optional $vivContext input
action-endpoint (FindOtherShoe) {
local-endpoint ("FindOtherShoe.js")
accepted-inputs (input1, input2, input3, $vivContext)
}

// simple local-endpoint using a specified function
action-endpoint (FindSock) {
local-endpoint ("FindSock.js::getSock")
accepted-inputs (input1, input2, input3, $vivContext)
}
}
}

You can then define an action implementation that returns values back to your capsule:

exports.tests = []
exports.preconditions = []

var SHOES = require("./lib/shoes");

// FindShoe
exports.function = function(name, type, minPrice, maxPrice) {
var result = SHOES.filter(function(shoe){
return (!name || shoe.name.toUpperCase() == name.toUpperCase()) &&
(!type || shoe.type.toUpperCase() == type.toUpperCase()) &&
(!maxPrice || shoe.price.value <= maxPrice.value) &&
(!minPrice || shoe.price.value >= minPrice.value)
})
return result
}

The parameters passed to the JavaScript action are set by the accepted-inputs key. In the example above, the parameters match the JavaScript function:

accepted-inputs (name, type, minPrice, maxPrice)

The order does not matter, but the names between accepted-inputs and the JavaScript function parameters must match.

If the accepted-inputs line is not given, Bixby maps the inputs from the Action model to the JavaScript function. The same effect as above could be achieved with this FindShoe.bxb action model:

action (FindShoe) {
type (Search)
collect {
input (name) {
type (Name)
min (Optional)
}
input (type) {
type (Type)
min (Optional)
}
input (minPrice) {
type (money.MinPrice)
min (Optional)
max (One)
}
input (maxPrice) {
type (money.MaxPrice)
min (Optional)
max (One)
}
}
output (Shoe)
}

If you include an empty accepted-inputs key, it will only map to the JavaScript function when there are no inputs at runtime. The following endpoint will never pass inputs to its JavaScript action:

action-endpoint (FindBusiness) {
accepted-inputs ()
local-endpoint (FindBusiness.js)
}

If any inputs are passed to FindBusiness, then FindBusiness.js will not be invoked.

Note

When using local endpoints, we enforce JavaScript execution resource limits.

CPU running time is limited to 25 seconds for each JavaScript action. This includes any aspect of the full JavaScript execution including JavaScript code bootstrap, interpretation, and function result or failure handling.

Additionally, memory allocation is limited to 65 MB.

Bixby will terminate violators of these limits, and they are subject to change in the future.

Remote Endpoints

For remote endpoints, you must declare the URL (remote-endpoint) of your remote HTTP server's API, the inputs to accept (accepted-inputs), and the HTTP method to use when calling the endpoint. Endpoints that accept parameters must use POST or PUT; you cannot use GET to pass any concepts from Bixby. Parameters are passed in the HTTP request body as a JSON object.

You can implement your remote endpoints using any HTTP server technology and language you choose. The only requirements are that they accept and return JSON objects that map to Bixby concepts. As an example, here is a Bixby concept from the HTTP API Call sample capsule and its JSON counterpart:

Bixby Concept Model

structure (Shoe) {
property (name) {
type (Name)
min (Required)
}
property (description) {
type (Description)
min (Required)
}
property (type) {
type (Type)
min (Required)
}
property (price) {
type (money.Price)
min (Required)
}

// The id property is returned by the API, but not used by Bixby
property (id) {
type (Id)
min (Optional)
}
}

JSON Representation

{
"id": 1,
"name": "Boots",
"description": "Covers the foot and the ankle and extends up the leg.",
"price": {
"value": 70,
"currencyType": {
"currencyCode": "USD",
"prefixSymbol": "$"
}
},
"type": "Boot"
}

In that sample capsule, the FindShoeRemoteEndpoint has a very simple model that is functionally identical to its local endpoint counterpart:

action (FindShoeRemoteEndpoint) {
type (Search)
output (Shoe)
}

In the endpoints.bxb file, this action has a matching remote endpoint defined. This is similar to how a local endpoint would be configured, but using the remote-endpoint key and specifying a URL rather than specifying a JavaScript file. In this case, the endpoint takes no parameters, and simply returns a JSON array of shoe objects like the one shown above.

endpoints {
action-endpoints {
action-endpoint (FindShoeRemoteEndpoint) {
accepted-inputs ()
remote-endpoint ("{remote.url}/shoes") {
method (GET)
}
}
}
}

In this example, the remote endpoint URL is specified in the capsule.properties file:

config.default.remote.url=https://bixby-http-demo.appspot.com

If this endpoint did take a parameter like its local endpoint counterpart, it would need to be listed in the accepted-inputs key.

accepted-inputs (type)

If you need to pass in HTTP headers, such as Content-Type: application/x-www-form-urlencoded or Content-Type: application/json, you can specify them using the header key within the remote-endpoint block:

endpoints {
action-endpoints {
action-endpoint (FindShoeRemoteEndpoint) {
accepted-inputs (type)
remote-endpoint ("{remote.url}/shoes") {
method (POST)
headers {
header (Content-Type: application/json)
}
}
}
}
}
Note

Remote endpoints are always passed $vivContext whether or not it is declared in the accepted-inputs key. See Accessing User Context for details.

The HTTP API sample capsule has a working demonstration of remote-endpoint in it, along with several demonstrations of using local endpoints with the HTTP API.

Localhost Tunneling

Since capsules are run through the Bixby platform, they don't have access to remote endpoint servers running on localhost; the endpoints must be publicly accessible. This can make development and testing of remote endpoints difficult. While you can address this by hosting and constantly updating the remote endpoint code, you can also use tunneling to forward requests from your publicly available IP to your local machine. There are many tools out there which can be used to achieve this, such as Pagekite, localtunnel, or ngrok.

Caution

Be aware that exposing an http server running on localhost publicly via tunneling has security implications that extend beyond the scope of this documentation. Please be cautious, do your research on security best practices, and only use test data when utilizing tools like those listed above.

Calling a Web Service from a Local Endpoint

If your capsule needs to interface with an existing web service that takes input or returns output in a format that's not directly compatible with a Bixby concept, you might not be able to use remote-endpoint. In this case, you can write a local endpoint that uses Bixby's JavaScript HTTP library to talk to your API.

Suppose the Remote Endpoints example above did not return a JSON representation of the Shoe concept, but instead returned another JSON structure:

{
"uuid": "0158139A-37A6-4748-8C5F-A0C36E97247B",
"name": "Boots",
"desc": "Covers the foot and the ankle and extends up the leg.",
"price": 70,
"currency": "USD",
"kind": "Boot"
}

By using a local endpoint, you could map the response onto the Shoe concept:

var http = require('http');
var console = require('console');
var config = require('config');

module.exports.function = function findShoe (type) {
console.log("FindShoe filter by a specific type");
var options = {
format: 'json',
query: {
type: type
}
};
var response = http.getUrl(config.get('remote.url') + '/shoes', options);
var currencySymbol = {
"USD": "$",
"EUR": "€"
};
var shoes = [];
for (var item of response) {
shoes.push({
"name": item.name,
"description": item.desc,
"price": {
"value": item.price,
"currencyType": {
"currencyCode": item.currency,
"prefixSymbol": currencySymbol[item.currency]
},
},
"type": item.kind
});
}
return shoes;
}

The query being sent to the remote server is in the query property of the options object. The config.get() JavaScript method is used to get the remote.url property from the capsule.properties file. The response variable is assumed to be an array of JSON objects; the for loop simply goes through each object, creating a new object in the proper format and adding it to the shoes array. Finally, shoes is returned to Bixby.

Note

Remember that the specifics of the HTTP response are dependent on what the web service API returns. You need to use that API's documentation as a guide to reformatting the response object and, if necessary, the HTTP query object. You can use the format option in the getUrl options to help parse different kinds of responses; consult the HTTP Options documentation for details.

For more examples of working with APIs from local endpoints, review the HTTP API Call sample capsule.

Accessing User Context

If the $vivContext parameter is passed to your endpoint, your action can read certain context information such as the user's locale, timezone, and certain device information. For local endpoints, $vivContext must be explicitly added to accepted-inputs; for remote endpoints, it is always passed to the remote API. Consider the following endpoint declaration:

endpoints {
action-endpoints {
action-endpoint (AccessVivContext) {
accepted-inputs (dummyInput, $vivContext)
local-endpoint ("AccessVivContext.js")
}
}
}

Now $vivContext can be accessed in your implementation function like any other JavaScript object.

module.exports.function = function accessVivContext(dummyInput, $vivContext) {
console.log($vivContext);
return $vivContext.locale;
}

For more details and a working demonstration, review the User Context sample capsule.

Conditionally Specifying Endpoints

You can use conditionals such as if-else to specify different endpoints, depending on your capsule's requirements.

...
// conditional remote-endpoint example ("main.url" specified in properties file)
// $vivContext is always passed to remote endpoints
action-endpoint (FindMyData) {
accepted-inputs (input1, input2, input3)
if ($vivContext.locale == 'fr_FR') {
remote-endpoint ("{main.url}/fr-path") {
method (POST)
}
}
else {
remote-endpoint ("{main.url}/en-path") {
method (POST)
}
}
}
...

Error Handling

If an API call produces an error (invalid or incomplete inputs, for example), you can return a JSON object and use an appropriate HTTP code, such as a 400 status code. In cases where there are server-side errors, the server returns a 500 status code. Generally status codes other than 200 are considered failures by the platform and will be thrown.

The JSON object returned should correspond to the same format as is for checked errors that your would define in your local function. For example, you might return a JSON error response like this:

{
$type: "checkedError", // error type, defines error thrown. For most errors, set to "checkedError"
$errorId: "MyErrorId", // error ID maps to ID of the checked error defined in the action
$message: "my error message", // internal message, not displayed to end-user
$errorObj: {prop1: "val1"} // optional JSON object matching the checked error defined in the action
}

The above sample corresponds to the checked error defined in the action, as shown here:

action (MyAction) {
type (search)
collect {
...
}
output (MyResult) {
throws {
error (MyErrorId) {
property (prop1) {
type (MyPropType)
min (Required) max (One)
}
on-catch {
halt { dialog { template ("Some error message: #{value(prop1)}") } }
}
}
}
}

You define the way errors are handled within your action. If no JSON object is defined, or no checked error is defined in the action, then it is treated as an unhandled error thrown from a local JavaScript function.

Here's an example of throwing a checkedError for a resetOrder:

// Exports
module.exports = {
function: resetOrder
}

function resetOrder(order) {
console.log("[Store] resetOrder...");
throw fail.checkedError( //checkedError is the error type
"Reset all data", //error message
"ResetAllData", //errorId
null); //no errorObj
return order;

The corresponding action is here:

action (ResetOrder) {
type (Constructor)
description (Reset all data includes cart and store)
collect {
input (order) {
type (Order)
min (Optional) max (One)
default-select {
with-rule {
select-first
}
}
}
}
output (Order) {
throws {
error (ResetAllData) {
on-catch {
replan {
intent {
goal { CreateOrder }
}
}
}
}
}
}
}

OAuth Authorization

If your endpoint (local, remote, or client) requires OAuth authorization, you can specify the authorization in the authorization.bxb file and include the authorization key while specifying the correct scope (either User or Global). If the shoe-finding example used OAuth, the authorization block might look like this:

authorization {
user {
oauth2-authorization-code (Shoebox) {
authorize-endpoint ("{remote.url}/authorize")
token-endpoint ("{remote.url}/token")
client-id (findshoes-shoebox-client)
client-secret-key (remote.key)
}
}
}

The remote endpoint is associated with this authorization by including the authorization (User) key:

endpoints {
action-endpoints {
action-endpoint (FindShoeRemoteEndpoint) {
accepted-inputs ()
remote-endpoint ("{remote.url}/shoes") {
method (GET)
}
authorization (User)
}
}
}

Authorization Errors

You might get an OAuth authorization failure if the token is invalid, or the user has manually revoked access through their provider. To handle authorization failures with local endpoints, your JavaScript implementation can throw the native authorization-error type to make Bixby ask the user to log in again.

function getData(input, $vivContext) {
var endpoint = 'https://data.example.com/feed/';
var query = {
token: $vivContext.accessToken,
searchKeys: input,
};

var response = http.oauthGetUrl(endpoint, {
format: 'json',
query: query
});

if (response.status == 200) {
return response.parsed;
}
else if (response.status == 401) {
var err = new Error('Authorization failure');
err.$type = 'authorization-error';
err.$message = 'Authorization failure';
throw err;
}
else {
console.error('Request failed:', response);
return null;
}
}