Uh oh! We couldn’t find any match.

Please try other search keywords.

Bixby Developer Center

Guides

Configuring Endpoints

Implementing Actions using Endpoints

Bixby supports the following ways to implement actions:

  • Local endpoints: JavaScript files included in your capsule code, executed in the Bixby platform's JavaScript environment.
  • Remote endpoints: The Bixby platform will issue requests against a publicly accessible web server that you control.

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

Advantages

  • You are no longer restricted to using JavaScript and the specific libraries we support; any HTTP server will do.
  • You can leverage an existing web server/API.

Disadvantages

  • You are responsible for the maintenance and uptime of your codebase and server.
  • When developing your remote endpoints, you must expose a public web server running your development code since the Bixby platform does not have access to your local addresses and hostnames like localhost (see the Remote Endpoints section for details on how you can achieve this).

You create action endpoints 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:

  • Apply to all locales:
    %capsulename%/resources/base/
  • Apply to a specific locale:
    %capsulename%/resources/en-us/

Endpoint definitions follow a similar format for all endpoint types.

endpoints {
action-endpoints {
action-endpoint (EndpointName) {
accepted-inputs (a, b, c, $vivContext)
local-endpoint ("ActionFilename.js")
}
}
}

Every action-endpoint specifies its accepted-inputs and a filename to call. In the case of a remote-endpoint, a URL to call would be specified.

The accepted-inputs key defines the parameters that are passed to the JavaScript action. If the accepted-inputs line is not given, Bixby will use the inputs defined in the Action model. The parameter names in the JavaScript function must match the names given in accepted-inputs (or, if that key is omitted, the inputs). The accepted-inputs key can also include the $vivContext parameter, to allow your JavaScript actions to access user-specific information.

Specifics and examples will be given for each kind of endpoint.

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.

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

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

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

You can then define your external 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

Remote endpoints allow you to connect the Bixby platform to your own remote HTTP server that you maintain. This often takes the form of an API microservice containing Bixby-specific business logic and calls to a content provider API, but the implementation is up to you.

You can implement your remote endpoints using any HTTP server technology you are comfortable with, such as Node/Express.js, Rails, Django, Spring, etc.

As with local endpoints, you specify accepted-inputs as properties of the request body. You must specify the remote-endpoint URL and HTTP method you want to use when calling the endpoint. Please note that Bixby concepts are passed in the request body, so GET cannot be used to pass any concepts from the Bixby platform.

...
// simple remote-endpoint example ("main.url" specified in properties file)
// $vivContext is always passed to remote endpoints
action-endpoint (FindMyData4) {
accepted-inputs (input1, input2, input3)
remote-endpoint ("{main.url}/path1") {
method (POST)
}
}
...

Perhaps you need to pass in headers, such as Content-Type: application/x-www-form-urlencoded or Content-Type: application/json. To do this, you can specify them using the header key:

...
// simple remote-endpoint example with optional headers
action-endpoint (FindMyData5) {
accepted-inputs (input1, input2, input3)
remote-endpoint ("{main.url}/path2") {
method (POST)
}
headers {
header (header-name: header-value)
}
}
...

If your endpoint (local, remote, or client) requires OAuth authorization, you can use the authorization child key of action-endpoints, which then applies it to all instances of action-endpoint. If you want to override or apply a specific authorization on a specific actin endpoint, you can use authorization-override. The following example includes OAuth-specific parameters like as provider and scope:

...
// local and remote-endpoints can specify use of OAuth
// OAuth defined in the root level applies to all endpoints,
// but each endpoint can override this
action-endpoint (FindMyData6) {
accepted-inputs (input1, input2, input3)
remote-endpoint ("{main.url}/path3") {
method (POST)
}
authorization-override {
// These are the same values you otherwise would define in
// JavaScript files directly
oauth {
provider (my-provider)
grant-type (authorization_code)
scope (my-scope)
client-id (my-client-id)
client-secret (my-secret)
authorize-endpoint (my-auth-endpoint)
token-endpoint (my-token-endpoint)
}
}
}
...

You should format response payloads from the remote endpoint as JSON. Mark successful responses with status 200.

Hosting

Since capsules are run through the Bixby platform, they don't have access to remote endpoint servers running on localhost. Therefore, when developing remote endpoints, they must be publicly accessible. You can accomplish this by hosting and constantly updating the remote endpoint code, or you can use tunnelling 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.

OAuth Authorization with Endpoints

Bixby has built-in support for endpoints that require OAuth 2.0 authorization. Use the authorization key and its subkeys to set the parameters for your capsule's external OAuth provider. You'll need to consult the documentation for your OAuth provider to find its endpoint URIs and the parameters that it supports.

You configure OAuth under the authorization key in an endpoints block:

endpoints {
authorization {
oauth {
provider (MY_PROVIDER)
client-id (MY_CLIENT_ID)
client-secret (MY_SECRET)
grant-type (client_credentials)
token-endpoint (auth_endpoints.js::tokenEndpoint)
revoke-endpoint (auth_endpoints.js::revokeEndpoint)
scope (some scope)
}
}

action-endpoints {
action-endpoint (TestAction) {
accepted-inputs (input, $vivContext)
local-endpoint (TestAction.js)
}
}
}

For an explanation of the oauth subkeys, see the oauth reference. Not all oauth definitions need to include all keys. Note that you could also declare OAuth authorization per endpoint using the authorization-override subkey.

Here's another example, using the Spotify Web API:

authorization {
oauth {
provider (Spotify)
client-id (MY_CLIENT_ID)
client-secret (MY_CLIENT_SECRET)
grant-type (authorization_code)
authorize-endpoint (https://accounts.spotify.com/authorize)
token-endpoint (https://accounts.spotify.com/api/token)
scope (playlist-read-private playlist-read-collaborative ...)
}
}

In some cases, you might define endpoints in a JavaScript file in your capsule; the first example above referred to an auth_endpoints.js file for endpoints. Such a file might look like this:

myTokenEndpoint = function (grantType, clientId, clientSecret, scope, code, redirectUri, refreshToken) {
// typically real work would be done here, but we just demonstrate the expected return object
return {
'access_token': 'abc456',
'expires_in': 5,
'refresh_token': 'xyz123'
}
}

myRevokeEndpoint = function (clientId, clientSecret, accessToken) {
// arbitrary handling here, we don't care what the CP does
}

module.exports.tokenEndpoint = myTokenEndpoint;
module.exports.revokeEndpoint = myRevokeEndpoint;

For another example, consider an action (TestAction) that requires OAuth authorization:

action (TestAction) {
type (Calculation)
collect {
input (input) {
type (TestConcept)
min (Required) max (One)
}
}
output (TestConcept)
}

The $vivContext parameter includes OAuth access tokens as accessToken. $vivContext is included automatically in the request body for remote endpoints. To include it in local endpoints, include it in the accepted-inputs key.

In this example, $vivContext is used to return the OAuth access token when a user logs off a service:

var authLib = require("lib/auth.js")

module.exports = {
function: logout
}

if (config.get('oauth.enable') == 'enable') {
module.exports.authorization = authLib.getAuthorization()
}

function logout($vivContext) {
return $vivContext.accessToken;
}

See Authorization Errors below for information on handling OAuth authorization errors in JavaScript actions.

Note

Bixby does not currently support the OAuth 1.0 protocol. Your capsule must authenticate with OAuth 2.0 providers.

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

Authorization Errors

You might get an authorization failure if the token is invalid, or the user has manually revoked access through their provider. To handle authorization failures, you 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;
}
}