Genie Cloud and Genie Server can be accessed from third-party applications using the following set of APIs.
For Genie Cloud, all API are accessible under https://genie.stanford.edu/me/api
, or the equivalent endpoint for a different Web Genie server. For Genie server, APIs are accessible from http://127.0.0.1/api
.
Authentication to the API differs whether you're targeting Genie Cloud or a local instance of Genie server.
Genie server API authentication can be configured in different modes, by setting the THINGENGINE_HOST_BASED_AUTHENTICATION
environment variable. If the variable is set to insecure
, all APIs are accessible without authentication. If the variable is set to local-ip
or proxied-ip
, APIs are accessible from the local loopback where Genie is running, as well as using an access token. Otherwise, API calls need an access token.
The token is configured in the prefs.db
configuration file of Genie, which is typically stored in ~/.config/almond-server
or /var/lib/almond-server
, depending of how Genie is installed. The access token is set as the access-token
key, and it is then used the same as an OAuth 2.0 access token (see below).
Web Genie uses the OAuth 2.0 Authorization Flow as specified in RFC 6749.
The first step in OAuth 2.0 is to call the authorization endpoint from the client. This will show a confirmation page to the user asking to grant access to Web Genie. After the user confirms the authorization, the browser will be redirected to the URI you specified, with a query parameter code
set to the temporary authorization code.
You must have a client ID to access this page. You can obtain both the client ID and the client secret from the developer portal.
GET /me/api/oauth2/authorize
Parameters:
response_type: "code"
client_id: "JIK283K"
redirect_uri: "http://yourapp.com/genie_auth"
scope: "profile user-read"
In the callback, you should pass the requested authorization scopes (space-separated), which must be a subset of the scopes you requested when you registered your client. Valid scopes are:
profile
: see the user's profileuser-read
: read active commands and devicesuser-read-results
: read results of active commandsuser-exec-command
: execute ThingTalk codedeveloper-read
: read unapproved devices (equivalent to a developer key)developer-upload
: upload new devicesdeveloper-admin
: modify thingpedia organization settings, add/remove members.You can request fewer scopes than those you registered for. Indeed, to ensure the best user experience and safety it is recommended to request the minimal scope necessary for the immediate functionality of your app, and upgrade as needed.
Example redirect: http://yourapp.com/genie_auth?code=ABCDEFGHIJ123456
If the user denies the authorization, you will receive a query parameter error=access_denied
.
After authorization, your client will exchange the authorization code to receive an access token, which can be used to issue authenticated requests.
POST /me/api/oauth2/token
Parameters:
grant_type: "authorization_code"
client_id: "JIK283K",
client_secret: "12345678901234"
code: "ABCDEFGHIJ123456"
redirect_uri: "http://yourapp.com/genie_auth"
Example access token response:
{
"access_token": "XYZIEOSKLQOW9283472KLW",
"refresh_token": "....",
"token_type": "Bearer",
"expires_in": "3600"
}
The redirect URI must be the same as originally passed to the /authorize
endpoint, or authentication will fail.
To support revocation, the access token expires quickly. In addition to the access token, you will receive a long-lived refresh token. You should store this in your database, and use it to obtain new access tokens at a later time.
The obtained access token can be used to issue authenticated requests by passing it into the Authorization
header:
Authorization: Bearer XYZIEOSKLQOW9283472KLW
All endpoints support this form of authentication.
Additionally, for compatibility with browser environments (where setting the Authorization
header is not possible), Web Socket endpoints also support passing the access token in the query string as the access_token
parameter:
GET /me/api/conversation?access_token=XYZIEOSKLQOW9283472KLW
This is not recommended unless absolutely necessary though, as it will result in the access token being logged in the clear in your browser history, web inspector, and elsewhere. Note in particular that server-side Web Socket libraries (such as ws for node.js) support setting the Authorization
header.
After the access token expires, you should request a new access token:
POST /me/api/oauth2/token
Parameters:
grant_type: "refresh_token"
client_id: "JIK283K",
client_secret: "12345678901234"
refresh_token: "...."
Example access token response:
{
"access_token": "XYZIEOSKLQOW9283472KLW",
"refresh_token": "....",
"token_type": "Bearer",
"expires_in": "3600"
}
Note that you might receive a different refresh token in response to this call, and you should update your database accordingly.
If the refresh token is invalid, or the user has revoked permission to your client, you will receive an invalid_grant
error. In that case, you should restart the authentication flow from the beginning.
Read the user's profile.
Method: GET
Scope: profile
GET /me/api/profile
Authorization: Bearer XYZIEOSKLQOW9283472KLW
HTTP/1.1 200 Ok
Content-Type: application/json
{
"id": "....",
"username": "bob",
"full_name": "Bob Builder",
"email": "bob@example.com",
"email_verified": 1,
"locale": "en-US",
"timezone": "America/Los_Angeles",
"model_tag": "default"
}
Use this endpoint to retrieve the details of the logged-in user, such as username, email and locale.
This endpoint uses the profile
scope, which is always available.
Open an interactive Web Genie conversation.
Method: Web Socket
Scope: user-exec-commands
GET /me/api/conversation
Connection: Upgrade
Upgrade: websocket
Authorization: Bearer XYZIEOSKLQOW9283472KLW
Operation on this web socket consists of sending and receiving messages to drive a single Genie conversation, which is automatically created upon connection and disposed of when the connection is closed.
For details on how to control a conversation with Genie, see the Genie Dialog API Reference.
Execute a single turn of an Genie conversation. This is the REST equivalent of /conversation
, and is provided for clients who cannot use Web Sockets.
Method: POST
Scope: user-exec-commands
POST /me/api/converse
Authorization: Bearer XYZIEOSKLQOW9283472KLW
Content-Type: application/json
{
"command": {
"type":"command",
"text":"what time is it?"
}
}
HTTP/1.1 200 Ok
Content-Type: application/json
{
"conversationId": "stateless-...",
"askSpecial": null,
"messages": [
{ "type": "text", "text": "Current time is 6:24:57 PM PDT.", "icon": "org.thingpedia.builtin.thingengine.builtin" }
]
}
The request body should contain a single message from the user to Genie. The response body will contain a list of messages from Genie. The body will also contain a conversationId
token that can be passed in the request body to subsequent calls to /converse
to preserve state. For details on the format of messages from the user and from Genie, see the Genie Dialog API Reference.
NOTE: after 5 minutes of inactivity, the conversationId is reset and the state of the conversation is lost. You can send a message containing a $wakeup;
ThingTalk command to keep the conversation alive.
Execute a single ThingTalk command.
Method: POST
Scope: user-exec-commands
POST /me/api/apps/create
Authorization: Bearer XYZIEOSKLQOW9283472KLW
Content-Type: application/json
{"code":"@com.thecatapi(id=\"com.thecatapi\").get();"}
HTTP/1.1 200 Ok
Content-Type: application/json
{
"uniqueId": "...",
"description": "get a cat picture",
"code": "@com.thecatapi(id=\"com.thecatapi\").get();",
"icon": "https://almond-static.stanford.edu/icons/com.thecatapi.png",
"results": [{
"outputType": "com.thecatapi:get",
"raw": {
"picture_url": "...",
"image_id": "...",
"link": "..."
},
"formatted": [{
"type": "rdl",
"webCallback": "...",
"displayText": "...",
}, {
"type": "picture",
"url": "..."
}]
}],
"errors": []
}
You can execute a single ThingTalk command and retrieve the results using the /apps/create
endpoint. You should pass a single JSON object as the request body, containing the ThingTalk code as the code
property. You should make sure that the code does not contain unfilled slots, including both $?
values and primitives without a selected device; to select a device, you should use the /devices/list
API or direct the user to configure the device in Genie.
The API will return the unique ID of the program; if the program is long-running, you can use this ID to stop it at a later time. The API will also return the description and icon of the program.
For each result returned by the command, you will receive a JSON object containing raw
and formatted
properties. raw
contains the ThingTalk values returned by the program, and is an object whose keys are the input and output parameters of the APIs in the program. formatted
contains a list of Genie messages, suitable for display in a conversation. See the Genie Dialog API Reference for details of the content of the formatted
field.
Get the list of configured Thingpedia devices.
Method: GET
Scope: user-read
GET /me/api/devices/list
Authorization: Bearer XYZIEOSKLQOW9283472KLW
HTTP/1.1 200 Ok
Content-Type: application/json
[{
"uniqueId": "com.imgur-...",
"name": "Imgur Account of ...",
"description": "Your Imgur Account, to browse and upload pictures",
"kind": "com.imgur",
"ownerTier": "global",
}, {
"uniqueId": "com.bing",
"name": "Bing Search",
"description": "Search the web, using Bing",
"kind": "com.bing",
"ownerTier": "global"
}]
Use this API to retrieve the unique ID, name, description, and kind of the configured Thingpedia devices. The API returns a list of JSON objects, one for each device. You should not assume that the list is sorted in any particular order.
Configure a new Thingpedia device.
Method: POST
Scope: user-exec-command
POST /me/api/devices/create
Authorization: Bearer XYZIEOSKLQOW9283472KLW
Content-Type: application/json
{
"kind": "io.home-assistant",
"hassUrl": "...",
"accessToken": "...",
"refreshToken": "...",
"accessTokenExpires": "...",
}
HTTP/1.1 200 Ok
Content-Type: application/json
{
"uniqueId": "io.home-assistant-...",
"name": "Home Assistant",
"description": "This is your Home Assistant Gateway.",
"kind": "io.home-assistant",
"ownerTier": "global",
}
This API provides low-level access to configure new devices, by-passing the normal configuration mechanism. One use-case for this API are API users that also have their own device in Thingpedia, and can generate access tokens for themselves without involving the user. The parameters are as defined by the device itself, with the exception of the kind
parameter which identifies the class in Thingpedia. The API returns the same object that would be returned by /devices/list
.
List active long-running ThingTalk commands.
Method: GET
Scope: user-read
GET /me/api/apps/list
Authorization: Bearer XYZIEOSKLQOW9283472KLW
HTTP/1.1 200 Ok
Content-Type: application/json
[{
"uniqueId": "...",
"description": "notify me when the latest Xkcd changes",
"code": "monitor (@com.xkcd(id=\"com.xkcd\").get_comic()) => notify;",
"icon": "https://almond-static.stanford.edu/icons/com.xkcd.png",
}]
Use this API to retrieve the unique ID, description, code, and icon of active long-running programs. The API returns a list of JSON objects, one for each command. If the command had an error, the object will contain an error
field with the error description.
Get details of a single active long-running ThingTalk commands.
Method: GET
Scope: user-read
GET /me/api/apps/get/123456
Authorization: Bearer XYZIEOSKLQOW9283472KLW
HTTP/1.1 200 Ok
Content-Type: application/json
{
"uniqueId": "123456",
"description": "notify me when the latest Xkcd changes",
"code": "monitor (@com.xkcd(id=\"com.xkcd\").get_comic()) => notify;",
"icon": "https://almond-static.stanford.edu/icons/com.xkcd.png",
}
Operation of this API is identical to /apps/list
, but it returns a single command by ID rather than all commands.
Stop an active long-running ThingTalk command.
Method: POST
Scope: user-exec-commands
GET /me/api/apps/delete/123456
Authorization: Bearer XYZIEOSKLQOW9283472KLW
HTTP/1.1 200 Ok
Content-Type: application/json
{
"status": "ok"
}
This API returns nothing on success, and an error on failure, e.g. if the ID does not refer to a running app.