Credential Type | HTTP BASIC AUTH | Session-based AUTH | Username | Password |
User | Yes | Yes | Chosen by user | Chosen by user |
Device | Yes | Not supported | Device UUID | Device Secret Key |
Credential Type | HTTP BASIC AUTH | Session-based AUTH | Username | Password |
User | Yes | Yes | Chosen by user | Chosen by user |
Device | Yes | Not supported | Device UUID | Device Secret Key |
POST /api/login { "username" : <USERNAME_OR_EMAIL>, "password" : <PASSWORD> }
POST /api/login { "username" : "leela", "password" : "P1anetExpre55" }
curl -c canopy.cookies -d '{ "username" : "leela", "password" : "P1anetExpre55" }' http://sandbox.canopy.link/api/login
Set-Cookie:canopy-login-session=<COOKIE_VALUE>Body:
200 OK { "result" : "ok", "username" : <USERNAME>, "email" : <EMAIL> }
200 OK { "result" : "ok", "username" : "leela", "email" : "leela@planetexpress.com" }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type |
incorrect_username_or_password Error | 401 | Incorrect username/password combo |
internal_error Error | 500 | Internal error occurred |
POST /api/logout
{ "result" : "ok" }
Error | Response Code | Reason |
internal_error Error | 500 | Internal error occurred |
To use BASIC AUTH, include the Base64-encoded "username:password" in every request. For example, if your username is "leela" and your password is "P1anetExpre55", include the following header with your request:
Authorization: Basic bGVlbGE6UDFhbmV0RXhwcmU1NQ==
Most HTTP libraries and tools can handle BASIC AUTH for you. For example:
curl -u leela:P1anetExpre55
$.ajax({ username : "leela", password : "P1anetExpre55", ... });
from requests.auth import HTTPBasicAuth requests.get(url, auth=HTTPBasicAuth('leela', 'P1anetExpre55'))
For a device with: - UUID: d5c827d2-1ba7-4da6-b87b-0864f0969fa8 - Secret Key: 00ae8a3ba42fa86765c885da90a7fcaec9898acb13fc2e00 You would: Base64Encode("d5c827d2-1ba7-4da6-b87b-0864f0969fa8:00ae8a3ba42fa86765c885da90a7fcaec9898acb13fc2e00") And then send this header: Authorization: Basic ZDVjODI3ZDItMWJhNy00ZGE2LWI4N2ItMDg2NGYwOTY5ZmE4OjAwYWU4YTNiYTQyZmE4Njc2NWM4 ODVkYTkwYTdmY2FlYzk4OThhY2IxM2ZjMmUwMA==
POST /api/create_user { "username" : string, "email" : string, "password" : string, // optional: "skip-email" : bool }
POST /api/create_user { "username" : "leela", "email" : "leela@planetexpress.com", "password" : "P1anetExpre55", "skip-email" : true }
curl -d '{ "username" : "leela", "email": "leela@planetexpress.com", "password" : "P1anetExpre55" }' http://sandbox.canopy.link/api/create_user
Field | Required | Description | Validation |
"username" | Required | Account username | string
|
"email" | Required | Account email | string
|
"password" | Required | Account password | string
|
"skip-email" | Optional | Defaults to false, meaning a welcome email will be sent to the user. If true the welcome email will not be sent. This is useful when running tests & simulators that should not send out lots of emails. | bool |
200 OK { "validated" : false, "email" : <EMAIL>, "result" : "ok", "username" : <USERNAME> }
200 OK { "validated" : false, "email" : "leela@planetexpress.com", "result" : "ok", "username" : "leela", }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure. |
email_taken Error | 400 | Email not available |
username_taken Error | 400 | Username not available |
internal_error Error | 500 | Internal error occurred |
GET /api/user/selfThis request requires that User credentials have been supplied (or a User is logged in), otherwise an error will be returned.
{ "result" : "ok", "validated" : <VALIDATED>, "username" : <USERNAME>, "email" : <EMAIL>, }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
POST /api/user/self { "email" : <NEW_EMAIL>, "old_password" : <OLD_PASSWORD>, "new_password" : <NEW_PASSWORD> }All fields are optional, although if "new_password" is provided then "old_password" must be provided as well. Omitted fields will not be modified.
{ "result" : "ok", "username" : <USERNAME>, "email" : <EMAIL>, }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
DELETE /api/user/self { "skip-email" : bool }
DELETE /api/user/self
curl -X DELETE -u "username:password" http://sandbox.canopy.link/api/user/self
Field | Required | Description | Validation |
"skip-email" | Optional | Defaults to false, meaning a welcome email will be sent to the user. If true the welcome email will not be sent. This is useful when running tests & simulators that should not send out lots of emails. | bool |
200 OK { "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
POST /api/create_devices { "quanitity" : <QUANTITY> "friendly_names" : [<FRIENDLY_NAME>, ...] }This will create new device resource(s) and assign the current User to have the "creator" role for those device(s). This will also automatically assign a UUID and Secret Key to each new device resource.
{ "result" : "ok", "count" : <COUNT>, "devices" : [ { "friendly_name" : <FRIENDLY_NAME>, "device_id" : <UUID>, "device_secret_key" : <DEVICE_SECRET_KEY> }, ... ] }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
TBD: Should there be a way to archive/suspend a device?
DELETE /api/device/<UUID>
DELETE /api/device/d5c827d2-1ba7-4da6-b87b-0864f0969fa8
DELETE /api/device/self
200 OK { "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
GET /api/user/self/devices? filter=<FILTER> &sort=<SORT> &limit=<LIMIT>Details about filtering, sorting, and paginating the results are described in the following sections.
<FILTER> is an expression encoded as a string that evaluates to a boolean value.
A <TERM> corresponds to a single boolean test. A "value test" has the form:
<VARIABLE> <RELATION> <VALUE>For example:
temperature >= 50.8Valid relations are:
Relation | Description |
= | Equals |
!= | Does not equal |
> | Greater than (numeric and date types only) |
>= | Greater than or equal to (numeric and date types only) |
< | Less than (numeric and date types only) |
<= | Less than or equal to (numeric and date types only) |
A "variable presence test" has the form:
HAS <VARIABLE>For example:
HAS humidity
The ! operator inverts the following TERM.
For example:!(HAS humidity) !(temperature < 10)The boolean operators && (AND) and || (OR) are supported:
<TERM> && <TERM> <TERM> || <TERM>For example:
temperature >= 50.8 && temperature < 90.5Valid terms may be the name of any cloud variable, or one of the system builtins that begin with system.
Key | Description | Potential Values |
system.ws_connection_status | Is websocket connected? |
|
system.activity_status | Has device communicated with remote recently |
|
<CLOUD_VAR_NAME> | Filter based on cloud variable value. | Depends on cloud variable datatype. |
!(temperature >= 50.8 && temperature < 90.5) || HAS humidity
<SORT> is a sequence of comma-separated keys specifying the sort order. The highest priority key comes first, followed by tie breakers. By default ascending order is used. To sort by a key in descending order, add ! before the key. For sort order determination, a missing cloud variable comes before the minimum value (i.e. at the front of the list when ascending, at the end of the list when descending).
This example sorts by temperature (ascending) then, to break ties, humidity (descending)
temperature,!humidityThe valid keys may be any cloud variable, or builtin. See Filtering Device List.
limit=<start>,<count>For example:
limit=0,50There is an implementation defined maximum limit to the number of results that will be returned. If count is above this number, then the results will be truncated and the response count field will tell you how many items are actually returned.
{ "result" : "ok", "count" : <COUNT>, "devices" : [ { "device_id" : <UUID>, "friendly_name" : <FRIENDLY_NAME>, "status" : { "ws_connected" : <WS_CONNECTED>, "active_status" : <ACTIVE_STATUS>, "last_activity_time" : <LAST_ACTIVITY_TIME>, }, }, ... ] }
GET /api/device/<UUID>? timestamps=<TIMESTAMPS>
GET /api/device/d5c827d2-1ba7-4da6-b87b-0864f0969fa8?timestamps=rfc3339
GET /api/device/self? timestamps=<TIMESTAMPS>
The /api/device/<UUID> form of the request obtains a specific device by UUID. The requester (i.e. the authenticated User or Device) must have permission to access the specified Device, otherwise an error will occur.
The second form of the request obtains the currently authenticated device (i.e. the device whose credentials were supplied via BASIC AUTH). It returns an error if User credentials are supplied.
TBD [If the provided device credentials are unknown to the remote, and the server supports anonymous devices, then a new anonymous device will be created. If the first form of the request is used, then an anonymous device will only be created if the UUID in the URI matches the UUID in the credentials, otherwise an error will occur.]
The following query parameters are accepted
Query Parameter | Required? | Description |
timestamps | Optional | Optionally select timestamp format. This may be either:
|
GET /api/device/<UUID>/<VAR_NAME>? start_time=<START_TIME> &end_time=<END_TIME> &hint_num_samples=<HINT_NUM_SAMPLES>
GET /api/device/d5c827d2-1ba7-4da6-b87b-0864f0969fa8/temperature?start_time='2015-03-19 12:00:00'
{ "result" : "ok", "current_clock_us" : uint64, "current_clock_utc" : string, "count" : int, "samples" : [ { "t" : string, "v" : value }, { ... } ] }
{ "result" : "ok", "current_clock_us" : 1426772581829000, "current_clock_utc" : "2015-03-19T22:47:31Z", "count" : 3, "samples" : [ { "t": "2015-03-18T16:57:36Z", "v": 24.821886 }, { "t": "2015-03-18T16:57:56Z", "v": 24.777397 }, { "t": "2015-03-18T16:58:17Z", "v": 24.777014 } ] }
Field | Description |
"clock_us" | Server time in microseconds since the Unix Epoch. |
"clock_utc" | Server time reported as an RFC3339-encoded UTC datetime string |
"count" | Integer number of samples returned. |
"samples" | Array of Sample objects. |
"t" | String datetime of sample in RFC3339 format with UTC timezone. |
"v" | Value of the cloud variable at the timestamp. The JSON datatype depends on the Cloud Variable's datatype. |
POST /api/device/<UUID> { "friendly_name" : string, "location_note" : string }
All fields are optional and omitted fields will be left unchanged.
This may be combined with Add/Update Cloud Var Metadata and Update Cloud Var Values into a single request.
Field | Description |
"friendly_name" | User-assigned name for device. May be any UTF-8 string of at most 127 bytes. |
"location_note" | User-assigned note about the location of this device. May be any UTF-8 string of at most 1023 bytes. |
This request may be combined with Declare Cloud Variable and Set Cloud Variable Values into a single request.
Before using a Cloud Variable you must declare it, specifying its name, direction, datatype and other metadata properties. The first time a cloud variable name is declared for a particular device, a cloud variable gets created, but not set. Declaring a cloud variable is an idempotent operation, meaning that subsequent identical declarations will have no effect. However, attempts to redeclare a named cloud variable with different datatype or direction will fail unless you delete it first.
POST /api/device/<UUID> { "var_decls" : { <VAR_DECL> : <VAR_PROPERTIES>, ... } }
POST /api/device/d5c827d2-1ba7-4da6-b87b-0864f0969fa8 { "var_decls" : { "out float32 temperature" : { }, "out float32 humidity" : { }, "in int8 dimmer_brightness" : { }, "in bool reboot_now" : { }, ... } }
A <VAR_DECL> is a string with the following format:
"<DIRECTION> <TYPE> <NAME>"For example:
"out float32 temperature"The <DIRECTION> determines who can modify the value:
Direction | Who Can Modify Value |
out | Can only be set by the device that the cloud variable belongs to. |
in | Can only be set by users and other devices (i.e. not the device that the cloud variable belongs to). |
inout | Anyone with access to the cloud variable can set it. |
Datatype | Description |
bool | Boolean value |
int8 | 8-bit signed integer |
int16 | 16-bit signed integer |
int32 | 32-bit signed integer |
uint8 | 8-bit unsigned integer |
uint16 | 16-bit unsigned integer |
uint32 | 32-bit unsigned integer |
datetime | 64-bit unsigned integer representing microseconds from Epoch |
float32 | 32-bit floating point number |
float64 | 64-bit floating point number |
string | string value |
TBD composite objects | TBD |
TBD reserved values?
The <VAR_PROPERTIES> is an object specifying additional metadata for the cloud variable. At this time it must be an empty object {} reserved for future use.
This request may be combined with Update Device Properties and Update Cloud Var Value into a single request.
To set Cloud Variable values, use:
POST /api/device/<UUID> { "vars" : { <VAR_NAME> : value, ... } }
POST /api/device/d5c827d2-1ba7-4da6-b87b-0864f0969fa8 { "vars" : { "temperature" : 43.0, "humidity" : 32.41, "dimmer_level" : 4, "happy" : true ... } }
The datatype of the value must match the datatype of the Cloud Variable.
This request may be combined with Update Device Properties and Declare Cloud Variable into a single request.
GET /api/device/<UUID> GET /api/device/self POST /api/device/<UUID> POST /api/device/self
{ "result" : "ok", "device_id" : <UUID>, "friendly_name" : <FRIENDLY_NAME>, "status" : { "ws_connected" : <WS_CONNECTED>, "active_status" : <ACTIVE_STATUS>, "last_activity_time" : <LAST_ACTIVITY_TIME>, }, "var_decls" : { <VAR_DECLS> }, "vars" : { <VARS> }, }
{ "result" : "ok", "device_id" : "d5c827d2-1ba7-4da6-b87b-0864f0969fa8", "friendly_name" : "My Toaster 17", "status" : { "ws_connected" : true, "active_status" : "active", "last_activity_time" : 1426803897000000, }, "var_decls" : { "out float32 temperature" : { }, "out float32 humidity" : { }, "in int8 dimmer_brightness" : { }, "in bool reboot_now" : { }, }, "vars" : { "temperature" : { "t" : 1426803897000000, "v" : 37.4, }, "humidity" : { "t" : 1426803897000000, "v" : 92.3, }, "dimmer_brightness" : { "t" : 1426803897000000, "v" : 0, }, "reboot_now" : { "t" : 1426803897000000, "v" : false, } } }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure; Attempt to change an existing cloud variable's declaration. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
url_not_found Error | 404 | Device with <UUID> does not exist or you have no access to it. |
internal_error Error | 500 | Internal error occurred |
POST /api/create_org { "name" : string, }
POST /api/create_org { "name" : "Planet-Express", }
curl -d '{ "name" : "Planet-Express" }' http://sandbox.canopy.link/api/create_org
Field | Required | Description | Validation |
"name" | Required | Organization name | string
|
200 OK { "name" : <ORG_NAME>, "result" : "ok", }
200 OK { "name" : "Planet-Express", "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | Non-JSON payload; Required fields missing or incorrect type; Input validation failure. |
email_taken Error | 400 | Email not available |
username_taken Error | 400 | Username not available |
internal_error Error | 500 | Internal error occurred |
DELETE /api/org/<ORG_NAME>
DELETE /api/org/Planet-Express
curl -X DELETE http://sandbox.canopy.link/api/org/Planet-Express
Field | Required | Description | Validation |
"name" | Required | Organization name | string
|
200 OK { "name" : <ORG_NAME>, "result" : "ok", }
200 OK { "name" : "Planet-Express", "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
url_not_found Error | 404 | Organization with <ORG_NAME> does not exist or you have no access to it. |
internal_error Error | 500 | Internal error occurred |
GET /api/user/self/orgs
200 OK { "orgs" : [ <ORG_NAME_0>, <ORG_NAME_1>, ... ], "result" : "ok", }
200 OK { "orgs" : [ "Planet-Express", "MomCorp", ] "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
GET /api/org/<ORG_NAME>/members
Only members of the organization are authorized to make this request.
200 OK { "members" : [ { "username" : <USERNAME>, "email" : <EMAIL>, "teams" : [ <TEAM_0>, <TEAM_1>, ... ] }, ... ], "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
POST /api/org/<ORG_NAME>/members { "add_member" : { "user" : <USERNAME_OR_EMAIL>, } }
Only owners of the Organization are authorized to make this request.
200 OK { "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | No User found with provided username or email address. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
POST /api/org/<ORG_NAME>/members { "remove_member" : <USERNAME_OR_EMAIL>, }
This request will also remove the member from all of the Organization's Teams that they are on.
Only owners of the Organization are authorized to make this request.
This request may be combined with an Add Member request, in which case addition occurs before removal.
200 OK { "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | No User found with provided username or email address. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
GET /api/org/<ORG_NAME>/teams
Only members of the Organization are authorized to make this request.
200 OK { "teams" : [ { "name" : <TEAM_NAME>, "url_alias" : <TEAM_URL_ALIAS> }, ... ] }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
url_not_found Error | 404 | Organization with <ORG_NAME> does not exist or you have no access to it. Team with <TEAM_URL_ALIAS> does not exist within this organization. |
internal_error Error | 500 | Internal error occurred |
POST /api/org/<ORG_NAME>/create_team { "name" : <TEAM_NAME> "url_alias" : <TEAM_URL_ALIAS> }
Only owners of the Organization are authorized to make this request.
200 OK { "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | Invalid team name |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
DELETE /api/org/<ORG_NAME>/team/<TEAM_URL_ALIAS>
Only owners of the Organization are authorized to make this request.
200 OK { "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
url_not_found Error | 404 | Organization with <ORG_NAME> does not exist or you have no access to it. Team with <TEAM_URL_ALIAS> does not exist within this organization. |
internal_error Error | 500 | Internal error occurred |
GET /api/org/<ORG_NAME>/team/<TEAM_URL_ALIAS>/members
Only members of the organization are authorized to make this request.
200 OK { "members" : [ { "username" : <USERNAME>, "email" : <EMAIL>, }, ... ], "result" : "ok", }
Error | Response Code | Reason |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
POST /api/org/<ORG_NAME>/team/<TEAM_URL_ALIAS>/members { "add_member" : { "user" : <USERNAME_OR_EMAIL>, } }
Only owners of the Organization are authorized to make this request.
Only members of the Organization may be added to the Team.
200 OK { "result" : "ok", }
Error | Response Code | Reason |
bad_input Error | 400 | No User found with provided username or email address. |
not_authenticated Error | 401 | Missing credentials: not logged in and missing BASIC AUTH |
internal_error Error | 500 | Internal error occurred |
GET /api/info
curl https://sandbox.canopy.link/api/info
200 OK { "result" : "ok", "service-name" : "Canopy Cloud Service", "version" : string, "clock_us" : uint64, "clock_utc" : string, "config" : { "allow-anon-devices" : boolean, "allow-origin" : string, "email-service" : string, "enable-http" : false, "enable-https" : true, "forward-other-hosts" : string, "hostname" : string, "http-port" : int, "https-cert-file" : string, "https-port" : int, "https-priv-key-file" : string, "js-client-path" : string, "log-file" : string, "sendgrid-username" : string, "web-manager-path" : string } }
200 OK { "result" : "ok", "service-name" : "Canopy Cloud Service", "version" : "0.9.2-beta" "clock_us" : 1426772581829000, "clock_utc" : "2015-03-19 22:47:31.893205, "config" : { "allow-anon-devices" : true, "allow-origin" : "", "email-service" : "sendgrid", "enable-http" : false, "enable-https" : true, "forward-other-hosts" : "", "hostname" : "dev02.canopy.link", "http-port" : 80, "https-cert-file" : "/etc/canopy/cert.pem", "https-port" : 443, "https-priv-key-file" : "/etc/canopy/key.pem", "js-client-path" : "/home/gregp/development/canopy/canopy-js-client", "log-file" : "/var/log/canopy/server.log", "sendgrid-username" : "canopy-mailer", "web-manager-path": "/home/gregp/development/canopy/canopy-app" }, }
Field | Description |
version | Version of the canopy server as a string. For example "0.9.2-beta". |
clock_us | Server time in microseconds since the Unix Epoch. |
clock_utc | Server time reported as an RFC3339-encoded UTC datetime string |
config | Server configuration settings. For details about the "config" fields, see Canopy Server: Configuration Settings. |
Error | Response Code | Reason |
internal_error Error | 500 | Internal error occurred |
200 OK { "result": "ok", ... }
An unsuccesful API call will have a 4XX or 4XX response code and include "result" : "error" in the response. The response will also include an "error_type" field along with additional details about the error if available.
400 BAD REQUEST { "result" : "error", "error_type" : "bad_input", "error_msg" : "String \"username\" expected" }
400 BAD REQUEST { "result" : "error", "error_type" : "email_taken", }
401 UNAUTHORIZED { "result" : "error", "error_type" : "incorrect_username_or_password", }
401 UNAUTHORIZED { "result" : "error", "error_type" : "not_authenticated", }
500 INTERNAL SERVER ERROR { "result" : "error", "error_type" : "internal_error", "error_msg" : "Unable to connect to database", "error_details" : "session has been closed", }
400 BAD REQUEST { "result" : "error", "error_type" : "username_taken", }
404 NOT FOUND { "result" : "error", "error_type" : "url_not_found", }