Bixby Developer Center

Guides

Configuring Endpoints

Bixby supports the following ways to implement action models:

  • Local endpoint: A JavaScript implementation of your action that 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.

These are the 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.

These are the 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. This is the endpoint.bxb file for the Shoe sample capsule:

endpoints {

action-endpoints {

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

action-endpoint (FindAccessories) {
accepted-inputs (shoe)
local-endpoint ("FindAccessories.js")
}
}
}

View master on GitHub

  • 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.
  • Not used here, 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 can 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 as 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 (name, type, minPrice, maxPrice, $vivContext)
}

// simple local-endpoint, specifying a function
action-endpoint (FindSock) {
local-endpoint (FindAccessory.js::getSock)
accepted-inputs (name, type, minPrice, maxPrice)
}
}
}

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

export const tests = [];
export const preconditions = [];

import SHOES from "./lib/shoes";

//FindShoe
export default 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;
}

View master on GitHub

The parameters passed to the JavaScript action are set by the accepted-inputs key. In the example above, the parameters match this 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 FindShoe.bxb action model contains all the necessary inputs:

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)
}

View master on GitHub

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 Shoe concept from the HTTP API Call sample capsule and a JSON representation of a Shoe:

This is the 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)
}

//unused properties returned from API
property (id) {
type (Id)
min (Optional)
}
property (photo) {
type (Photo)
min (Optional)
}
}

View master on GitHub

This is its JSON representation:

  {
name: "Mules",
description: "No fitting around the heel (i.e. they are backless).",
price: {
value: 65,
currencyType: {
currencyCode: "USD",
prefixSymbol: "$"
}
},
images: [{url:'/images/mules.jpg'}],
type: "Formal"
},

View f4f5d14 on GitHub

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

action (FindShoeRemoteEndpoint) {
type (Search)
description (Demonstrate how to use remote endpoints for a simple search.)
output (Shoe)
}

View master on GitHub

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.

    action-endpoint (FindShoeRemoteEndpoint) {
accepted-inputs ()
remote-endpoint ("https://my-json-server.typicode.com/bixbydevelopers/capsule-samples-collection/shoes") {
method (GET)
}
}

View b59451b on GitHub

The {remote.url} in the remote-endpoint specification is a reference to a property specified in the capsule.properties file:

config.default.remote.url=http://shoe.bixby.pro

View master on GitHub

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.

Call 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 an entirely different 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:

import http from 'http'
import console from 'console'
import config from 'config'

export function findShoe(input) {
const { type, $vivContext } = input
console.log('FindShoe filter by a specific type')
const options = {
format: 'json',
query: {
type: type,
},
}
const response = http.getUrl(config.get('remote.url') + '/shoes', options)
const currencySymbol = {
USD: '$',
EUR: '€',
}
let shoes = []
for (let 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.

User Context Access

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 ($vivContext)
local-endpoint ("AccessVivContext.js")
}
}
}

View master on GitHub

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

// Request access to the $vivContext by declaring it as a parameter
export default function accessVivContext({ $vivContext }) {
return JSON.stringify($vivContext, undefined, 1);
}

View master on GitHub

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

Conditionally Specified 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 (FindItem) {
if ($vivContext.locale == 'fr_FR') {
remote-endpoint ("{main.url}/fr/item") {
method (GET)
}
}
else {
remote-endpoint ("{main.url}/en/item") {
method (GET)
}
}
}
...

Error Handling

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

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

{
$type: "checkedError",
$errorId: "NotFound",
$message: "item not found",
$errorObj: { message: "The request item is not available" }
}
  • $type: the error type being thrown; for most errors, keep this "checkedError"
  • $errorId: the ID of the error as defined in the action
  • $message: an internal message not displayed to the user
  • $errorObj: an optional JSON object returned to the action

The $errorObj returned in this example is a simple key-value pair with a message shown to the user. You could also return a more complex object for the error handler, or even an existing capsule model.

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

action (FindItem) {
type (search)
collect {
...
}
output (Item) {
throws {
error (NotFound) {
property (message) {
type (Message)
min (Required) max (One)
}
on-catch {
halt {
dialog {
template ("Sorry! #{value(message)}.")
}
}
}
}
}
}
}

The error handler above executes a halt effect, which stops execution. Your error handlers can use any effect in error handling, such as dropping an input and continuing execution with drop, or specifying a new intent and goal with replan. For example, a resetOrder action might create a new empty order by first throwing an error in its JavaScript code:

import console from 'console'
import fail from 'fail'

export function resetOrder(input) {
const { order } = input
console.log('[Store] resetOrder...')
throw fail.checkedError(
// checkedError is the error type
'Reset all data', // error message
'ResetAllData', // errorId
null,
) // no errorObj
return order
}

Then in the corresponding action, replan creates a new order:

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 }
}
}
}
}
}
}
}

For more detailed examples of error handling in action, download and read the documentation for the error handling sample capsule.

Video Tutorial: Error Handling in Bixby

The following video tutorial shows how to handle errors with the Input Validation and Error Handling sample capsule.

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 built-in authorization-error type to make Bixby ask the user to log in again.

import console from 'console'

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

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

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

Endpoints and Permissions

An endpoint might require one or more types of permissions to be granted by the user in order to execute the action. These actions must specify the required-permissions key.

action-endpoint (MyAction) {
local-endpoint (MyAction.js)
required-permissions {
permission (contacts)
}
}