Introduction
Hello! So you want to take your Exist data and do interesting things with it. Great! First things first, though: the API can only be accessed by user accounts. Sign up if you don’t yet have an account. Once you do, great! You can access all features of the API including writing data to attributes.
If you’re building something on the Exist API we encourage you to sign up to the API mailing list so we can update you on progress (typically we only email a few times a year).
Getting started
The API lives at https://exist.io/api/1/. All requests must use HTTPS.
POST bodies can be sent as application/json, application/x-www-form-urlencoded, or multipart/form-data.
However the API will only return JSON. XML is so last decade.
Requests are currently rate-limited at 300/hr per user token. Given user data does not change frequently within an hourly period (if at all) this should be more than adequate.
Wherever you see the username argument in a URL you may substitute the special keyword $self to request the authenticated user.
OAuth2 is the main means of authorising requests to the API, but if you’re building a personal read-only client you might like to use simple token auth. Read the authentication overview to see which one you need.
To get stuck in and retrieve some personal data, you should start by creating a new client app, then get today’s overview.
Important values
| Name | Value |
|---|---|
| API base URL | https://exist.io/api/1/ |
| OAuth2 base URL | https://exist.io/oauth2/ |
| Response type | application/json |
| Rate-limiting | 300 requests/hr per user token |
| POSTing data | application/x-www-form-urlencoded (the usual) or send the body as application/json |
| OAuth2 auth header | Authorization: Bearer [tokenxyz] |
| Simple token auth header | Authorization: Token [tokenabc] |
| Getting a simple token | POST 'username' and 'password' to https://exist.io/api/1/auth/simple-token/ |
| Testing your token | GET https://exist.io/api/1/users/$self/today/ |
| See some JSON in your browser | https://exist.io/api/1/users/$self/today/ |
Object types and terminology
Users
{
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "I made this thing you're using.",
"url": "http://hellocode.co/",
"avatar": "https://exist.io/static/media/avatars/josh_2.png",
"timezone": "Australia/Melbourne",
"local_time": "2020-05-08T20:56:20.417+10:00",
"private": false,
"imperial_units": false
}
Users are pretty self-explanatory.
Most of the time you will only be concerned with the currently authenticated user, so
wherever you see the username argument in a URL you may substitute the special keyword $self to request the
authenticated user.
Attributes
{
"attribute": "steps",
"label": "Steps",
"group":
{
"name": "activity",
"priority": 1
},
"priority": 1,
"service": "Fitbit",
"value_type": 0,
"value_type_description": "Integer",
"private": false,
"values": [
{
"value": 3725,
"date": "2015-05-08"
},
{
"value": 6177,
"date": "2015-05-07"
},
{
"value": 7811,
"date": "2015-05-06"
},
{
"value": 6632,
"date": "2015-05-05"
},
{
"value": 6014,
"date": "2015-05-04"
},
{
"value": 4376,
"date": "2015-05-03"
},
{
"value": 8671,
"date": "2015-05-02"
},
{
"value": 4658,
"date": "2015-05-01"
}
]
}
This is our name for data points or individual numbers a user can track about themselves. These are tracked at a single day granularity — there’s no per-hour or -minute data. Attributes can be strings but are usually quantifiable, ie. integers or floats.
An attribute has many values, one for each day that it has been tracked. In some responses, you’ll see the value property
reflected within an attribute object. At other times, particularly if you are requesting multiple days of data, the values property will
contain an array of date/value pairs.
If there is no data for a particular date, this will be reflected with a null value — you should expect to receive a list of results containing every single day, rather than days without data being left out.
All datetimes are in UTC unless otherwise specified, and should have the user’s timezone applied to create a local TZ-aware datetime. All dates are local to the user.
Values are always stored internally and returned in metric units. Each user object contains an imperial_units boolean which must be respected when formatting values for the user,
ie. if this is true, a steps_distance value must be converted from km to miles.
In situations where multiple attributes are requested, for example the “today” overview, these attributes will be returned grouped.
A group represents attributes that belong together, for example the “activity” group contains the attributes steps, active_min, etc.
You can see an example of this in the ‘overview’ endpoint.
Clients should display attributes in these groups when displaying multiple attributes. Groups are currently fairly broad and may change as we add more supported attributes.
See list of supported attributes.
Correlations
{
"date": "2015-05-03",
"period": 90,
"attribute": "steps",
"attribute2": "floors",
"value": 0.697409107405056,
"p": 2.22989517679296e-14,
"first_person": "I am much more likely (70%) to climb floors on days I take more steps.",
"second_person": "You are much more likely (70%) to climb floors on days you take more steps."
}
Correlations are a measure of the relationship between two variables. A positive correlation implies that as one variable increases (its values get higher), so does the other. A negative correlation implies that as one variable increases, the other decreases. We present these to users as a way to explain past trends, and use them to predict future behaviour.
Correlation values vary between -1 and +1 with 0 implying no correlation. Correlations of -1 or +1 imply an exact linear relationship.
P-values are a way of determining confidence in the result. A value lower than 0.05 is generally considered “statistically significant”.
We create simple English sentences to represent each possible correlation as a combination of attributes.
We’re also careful to represent these as correlations only, not as one attribute directly causing a change in the other, and we ask that you do the same. Correlation is not causation. There may be many hidden factors which underlie the relationship between two attributes. It is up to the user to determine the cause.
Correlations are generated weekly, so past values are also available in order to chart their changing strength.
Examples
“You are moderately more likely (55%) to be productive when you listen to music.”
In this case the value is 0.55, and this is a positive correlation — when one value (tracks played) increases, so does the other (time spent productively).
“You are somewhat less likely (25%) to be active when it’s warmer in the day.”
In this case the value is -0.25, and this is a negative correlation — when one value (max temp) increases, the other decreases (time active).
Insights
{
"created": "2015-05-08T05:05:46Z",
"target_date": "2015-05-07",
"html": "<div class=\"secondary\">Thursday marks a new productivity streak!</div>",
"value": "3 days",
"value2": "3",
"comment": null,
"type": {
"name": "productive_min_goal_best_streak",
"period": 1,
"priority": 2,
"attribute": {
"name": "productive_min",
"label": "Productive time",
"group": {
"name": "productivity",
"priority": 2
},
"priority": 1
}
}
}
Insights are interesting events found within the user’s data. These are not triggered by the user but generated automatically if the user’s data fits an insight type’s criteria. Typically these fall into a few categories: day-level events, for example, yesterday was the highest or lowest steps value in however many days; and week and month-level events, like summaries of total steps walked for the month. If an insight is relevant to a specific day it will contain a target_date value.
Insights have a priority where 1 is highest and means real-time, 2 is day-level, 3 is week, and 4 is month.
HTML output is provided, however each insight could be assembled from the value fields and knowledge of the insight type, if required.
Examples
Thursday marks a new productivity streak! Beat your goal every day for 3 days
Averages
{
"attribute": "floors",
"date": "2015-05-03",
"overall": 13,
"monday": 13,
"tuesday": 16,
"wednesday": 13,
"thursday": 12,
"friday": 14,
"saturday": 13,
"sunday": 13
}
Averages are generated weekly and are the basis of our goal system. For attributes that don’t warrant a specific related attribute_name_goal attribute, the average is used to create the “end value” of the attribute’s progress bar in our dashboard — meaning each day, users are being shown their progress relative to their usual behaviour. We break down averages by day of the week but also record the overall average. As we keep historical data this allows us to plot “rolling averages” showing changes in attribute values.
Note: these are actually medians, but we use “average” as it’s simpler to explain to users. Please also use this terminology.
Clients and services
We use client to refer an application with OAuth2 client credentials. A client which writes data to attributes is termed a service.
List of attributes
All attributes we currently support. The group an attribute belongs to may change in future, but attribute names should be considered stable.
Remember all data should be sent, and is stored and returned, in metric units. Imperial conversion should occur when rendering as required.
See attribute definition.
| Name | Group | Value type |
|---|---|---|
steps |
Activity | Integer |
steps_active_min |
Activity | Integer |
steps_elevation |
Activity | Float (km) |
floors |
Activity | Integer |
steps_distance |
Activity | Float (km) |
cycle_min |
Activity | Duration (minutes as integer) |
cycle_distance |
Activity | Float (km) |
workouts |
Workouts | Integer |
workouts_min |
Workouts | Duration (minutes as integer) |
workouts_distance |
Workouts | Float (km) |
productive_min |
Productivity | Duration (minutes as integer) |
neutral_min |
Productivity | Duration (minutes as integer) |
distracting_min |
Productivity | Duration (minutes as integer) |
commits |
Productivity | Integer |
tasks_completed |
Productivity | Integer |
words_written |
Productivity | Integer |
emails_sent |
Productivity | Integer |
emails_received |
Productivity | Integer |
coffees |
Food and drink | Integer |
alcoholic_drinks |
Food and drink | Integer |
energy |
Food and drink | Float (kJ) |
water |
Food and drink | Integer (ml) |
carbohydrates |
Food and drink | Float (g) |
fat |
Food and drink | Float (g) |
fibre |
Food and drink | Float (g) |
protein |
Food and drink | Float (g) |
sodium |
Food and drink | Float (mg) |
cholesterol |
Food and drink | Float (mg) |
sugar |
Food and drink | Float (mg) |
caffeine |
Food and drink | Float (mg) |
money_spent |
Finance | Float (user’s local currency unit) |
mood |
Mood | Integer (between 1 and 5 inclusive) |
mood_note |
Mood | String (max 255 characters) |
sleep |
Sleep | Duration (minutes as integer) |
time_in_bed |
Sleep | Duration (minutes as integer) |
sleep_start |
Sleep | Time of day (minutes from midday as integer) |
sleep_end |
Sleep | Time of day (minutes from midnight as integer) |
sleep_awakenings |
Sleep | Integer |
events |
Events | Integer |
events_duration |
Events | Duration (minutes as integer) |
weight |
Health | Float (kg) |
body_fat |
Health | Float (percentage, 0.0 to 1.0) |
heartrate |
Health | Integer |
meditation_min |
Health | Duration (minutes as integer) |
checkins |
Location | Integer |
location |
Location | String ("lat,lng" format where lat and lng are floats) |
tracks |
Media | Integer |
articles_read |
Media | Integer |
instagram_posts |
Social | Integer |
instagram_comments |
Social | Integer |
instagram_likes |
Social | Integer |
instagram_username |
Social | String |
tweets |
Integer | |
twitter_mentions |
Integer | |
twitter_username |
String | |
weather_temp_max |
Weather | Float (degrees Celsius) |
weather_temp_min |
Weather | Float (degrees Celsius) |
weather_precipitation |
Weather | Float (inches of water per hour) |
weather_cloud_cover |
Weather | Float (percentage of sky covered, 0.0 to 1.0) |
weather_wind_speed |
Weather | Float (km/hr) |
weather_summary |
Weather | String |
weather_icon |
Weather | String (name of icon best representing weather values) |
sunrise |
Weather | Time of day (minutes from midnight as integer) |
sunset |
Weather | Time of day (minutes from midday as integer) |
Authentication overview
There are two authentication methods — simple tokens, and OAuth2 clients. So which one do you need?
Simple token authentication is read-only and exists as a basic means for users to access their own data from Exist. This method is available to everyone by exchanging a username and password for a token that doesn’t expire. This is only recommended for quickly building a single-user, read-only client, and may be deprecated in future.
OAuth2 clients are superior to simple-token authentication as they can acquire control of attributes and write values for attributes. One client application can create and use access tokens for many users. One user may have many clients authorised to access their Exist account, each with a separate token that can be revoked.
OAuth2 clients are now available to all users. You can create one from your app management page within your Exist account.
Simple token authentication
All endpoints require authentication. We use a simple token-based scheme which allows a single token per user.
Make sure this token is included in your requests by including the Authorization header with every request.
If you are logged in to Exist in the browser your session-based authentication will also work. This is handy for browsing the API (assuming you’ve set up your browser to accept JSON) but shouldn’t be relied on for programmatic access.
Requesting a token
curl https://exist.io/api/1/auth/simple-token/ -d username=bobby_tables -d password=existrulz123
import requests
requests.post('https://exist.io/api/1/auth/simple-token/',
{'username':'bobby_tables','password':'existrulz123'})
Returns a token object in JSON:
{ "token": "96524c5ca126d87eb18ee7eff408ca0e71e94737" }
Exchange your user credentials for a token. This token will not change or expire by design but may be deprecated as we move to OAuth2 in the future.
Request
POST /api/1/auth/simple-token/
Parameters
| Key | Example value |
|---|---|
username |
bobby_tables |
password |
existrulz123 |
Response
A JSON object containing a token key.
Signing requests
Include the Authorization: Token [your_token] header in all requests.
Include the “Authorization: Token xyz” header in all requests.
import requests
requests.post(url,
headers={'Authorization':'Token 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
# With curl, you can just pass the correct header with each request
curl "api_endpoint_here"
-H "Authorization: Token 96524c5ca126d87eb18ee7eff408ca0e71e94737"
OAuth2 authentication
All endpoints require authentication, except those that are part of the OAuth2 authorisation flow.
Make sure your access token is included in your requests by including the Authorization: Bearer header with every request.
You may mix and match OAuth2 authentication with simple token or even session-based authentication as you test the API. API endpoints will respond with JSON within your browser if you are logged in to the site.
Authorisation flow
Send your user to the authorisation page at
https://exist.io/oauth2/authorize
# We can't really do this from the shell, but your URL would look like this:
curl https://exist.io/oauth2/authorize?response_type=code&client_id=[your_id]&redirect_uri=[your_uri]&scope=[your_scope]
# in django, we would do something like this
return redirect('https://exist.io/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s' % (CLIENT_ID, REDIRECT_URI,"read+write"))
User authorises your client by hitting 'Allow’, and Exist returns the user to your
redirect_uriwithcode=[some_code]in the query string. Exchange your code for an access token:
curl -X POST https://exist.io/oauth2/access_token -d "grant_type=authorization_code" -d "code=[some_code]" -d "client_id=[your_id]" -d "client_secret=[your_secret]" -d "redirect_uri=[your_uri]"
import requests
url = 'https://exist.io/oauth2/access_token'
response = requests.post(url,
{'grant_type':'authorization_code',
'code':code,
'client_id':CLIENT_ID,
'client_secret':CLIENT_SECRET,
'redirect_uri':REDIRECT_URI })
Returns JSON if your request was successful:
{ "access_token": "122bb8707b6aee134e7746a40feca41868ddd578", "token_type": "Bearer", "expires_in": 31535999, "refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9", "scope": "read write read+write" }
The OAuth2 authorisation flow is vastly simpler than the original OAuth 1.0:
- Send your user to the “request authorisation” page at
/oauth2/authorizewith these parameters:-
response_type=codeto request an auth code in return -
redirect_uriwith the URI to which Exist returns the user (must be HTTPS) -
scope=readorscope=read+writeto request read or read/write permissions -
client_idwhich is your OAuth2 client ID
-
- User authorises your application within the requested scopes (by hitting 'Allow’ in the browser)
- Exist returns the user to your
redirect_uri(GET request) with the following:-
codeparameter upon success -
errorparameter if the user didn’t authorise your client, or any other error with your request
-
- Exchange this code for an access token by POSTing to
/oauth2/access_tokenthese parameters:-
grant_type=authorization_code -
codewith the code you just received -
client_idwith your OAuth2 client ID -
client_secretwith your OAuth2 client secret -
redirect_uriwith the URI you used earlier
-
- If successful you will receive a JSON object with an
access_token,refresh_token,token_type,scope, andexpires_intime in seconds.
Refreshing an access token
curl -X POST https://exist.io/oauth2/access_token -d "grant_type=refresh_token" -d "refresh_token=[token]" -d "client_id=[your_id]" -d "client_secret=[your_secret]"
import requests
url = 'https://exist.io/oauth2/access_token'
response = requests.post(url,
{'grant_type':'refresh_token',
'refresh_token':token,
'client_id':CLIENT_ID,
'client_secret':CLIENT_SECRET
})
Returns JSON if your request was successful:
{ "access_token": "122bb8707b6aee134e7746a40feca41868ddd578", "token_type": "Bearer", "expires_in": 31535999, "refresh_token": "ac45027ad037f53b3ce91be272b163f55a4a87e9", "scope": "read write read+write" }
Tokens expire in a year and can be refreshed at any time, invalidating the original access and refresh tokens.
Request
POST /oauth2/access_token
Parameters
| Name | Description |
|---|---|
refresh_token |
The refresh token previously received in the auth flow |
grant_type |
refresh_token |
client_id |
Your OAuth2 client ID |
client_secret |
Your OAuth2 client secret |
Response
The same as your original access token response, a JSON object with an access_token, refresh_token, token_type, scope, and expires_in time in seconds.
Signing requests
import requests
requests.post(url,
headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
# With curl, you can just pass the correct header with each request
curl "api_endpoint_here"
-H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737"
Sign all authenticated requests by adding the Authorization header, Authorization: Bearer [access_token]. Note that this differs from the simple token-based authentication by using Bearer, not Token.
Users
Get current overview for user
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/today/
import requests
requests.get("https://exist.io/api/1/users/$self/today/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a user object in JSON:
{
"id": 1,
"username": "josh",
"first_name": "Josh",
"last_name": "Sharp",
"bio": "I made this thing you're using.",
"url": "http://hellocode.co/",
"avatar": "https://exist.io/static/media/avatars/josh_2.png",
"timezone": "Australia/Melbourne",
"local_time": "2020-07-31T22:33:49.359+10:00",
"private": false,
"imperial_units": false,
"attributes": [
{
"group": "activity",
"label": "Activity",
"priority": 1,
"items": [
{
"attribute": "steps",
"label": "Steps",
"value": 258,
"service": "Fitbit",
"priority": 1,
"private": false,
"value_type": 0,
"value_type_description": "Integer"
},
{
"attribute": "floors",
"label": "Floors",
"value": 2,
"service": "Fitbit",
"priority": 2,
"private": false,
"value_type": 0,
"value_type_description": "Integer"
}
]
}
]
}
Returns an overview of the user’s personal details, and their grouped attributes containing current values. This is analogous to the “Today” tiles in the dashboard.
When requesting the currently authenticated user, you will receive all attributes.
For other public users, private attributes will not be returned. For other private users, this will return a user stub.
Request
GET /api/1/users/:username/today/
Attributes
Get multiple attributes
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/attributes/
import requests
requests.get("https://exist.io/api/1/users/$self/attributes/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a list of attribute objects each with a list of values, by default all attributes:
[
{
"attribute": "steps",
"label": "Steps",
"group": "activity",
"service": "Fitbit",
"private": false,
"values": [
{
"value": 331,
"date": "2014-08-01"
},
{
"value": 5872,
"date": "2014-07-31"
},
{
"value": 2832,
"date": "2014-07-30"
},
{
"value": 5153,
"date": "2014-07-29"
},
{
"value": 4354,
"date": "2014-07-28"
},
{
"value": 7132,
"date": "2014-07-27"
},
{
"value": 4144,
"date": "2014-07-26"
}
]
},
{
"attribute": "floors",
"label": "Floors",
"group": "activity",
"service": "Fitbit",
"private": false,
"values": [ ]
}
]
Return the user’s attributes, all attributes with the last week’s values by default. Currently this method is only allowed for the authenticated user.
If you need to get a specific date range or combination of attribute values, you may combine the optional parameters to do so.
Request
GET /api/1/users/:username/attributes/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return, starting with today. Optional, max is 31. |
attributes |
Optional comma-separated list of attributes, eg. mood,mood_note |
date_min |
Optional date specifying the oldest value that can be returned, in format YYYY-mm-dd. |
date_max |
Optional date specifying the newest value that can be returned, in format YYYY-mm-dd. |
Get a specific attribute
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/attributes/steps/
import requests
requests.get("https://exist.io/api/1/users/$self/attributes/attribute/steps/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a list of attribute values, omitting the attribute object itself:
{
"count": 655,
"next": "https://exist.io/api/1/users/josh/attributes/steps/?page=2",
"previous": null,
"results": [
{
"value": 3783,
"date": "2015-05-08"
}, {
"value": 6177,
"date": "2015-05-07"
}, {
"value": 7811,
"date": "2015-05-06"
}, {
"value": 6632,
"date": "2015-05-05"
}, {
"value": 6014,
"date": "2015-05-04"
}, {
"value": 4376,
"date": "2015-05-03"
}, {
"value": 8671,
"date": "2015-05-02"
}
]
}
Returns a paged list of all values from an attribute.
Request
GET /api/1/users/:username/attributes/:attribute/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return per page. Optional, max is 100. |
page |
Page index. Optional, default is 1. |
date_min |
Oldest date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
date_max |
Most recent date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
Insights
Get all insights
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/insights/
import requests
requests.get("https://exist.io/api/1/users/$self/insights/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a JSON object containing a paged array:
{
"count": 740,
"next": "https://exist.io/api/1/users/josh/insights/?page=2",
"previous": null,
"results": [
{
"created": "2015-05-09T01:00:02Z",
"target_date": "2015-05-08",
"html": "<div class=\"secondary\">Friday night: Shortest sleep for 3 days</div>...",
"text": "Friday night: Shortest sleep for 3 days\r\n",
"type": {
"name": "sleep_worst_since_x",
"period": 1,
"priority": 2,
"attribute": {
"name": "sleep",
"label": "Time asleep",
"group": {
"name": "sleep",
"priority": 3
},
"priority": 2
}
}
},
{
"created": "2015-05-08T21:00:03Z",
"target_date": null,
"html": "<div class=\"number\">09:38</div>...",
"text": "09:38 average time asleep...",
"type": {
"name": "sleep_end_average_week",
"period": 7,
"priority": 3,
"attribute": {
"name": "sleep_end",
"label": "Wake time",
"group": {
"name": "sleep",
"priority": 3
},
"priority": 4
}
}
}
]
}
Returns a paged list of user’s insights. Only available for the currently authenticated user.
Request
GET /api/1/users/:username/insights/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return per page, starting with today. Optional, max is 100. |
page |
Page index. Optional, default is 1. |
date_min |
Oldest date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
date_max |
Most recent date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
Get all insights for attribute
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/insights/attribute/sleep/
import requests
requests.get("https://exist.io/api/1/users/$self/insights/attribute/sleep/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a JSON object containing a paged array:
{
"count": 220,
"next": "https://exist.io/api/1/users/josh/insights/attribute/sleep/?page=2",
"previous": null,
"results": [
{
"created": "2015-05-09T01:00:02Z",
"target_date": "2015-05-08",
"html": "<div class=\"secondary\">Friday night: Shortest sleep for 3 days</div>...",
"text": "Friday night: Shortest sleep for 3 days...",
"type": {
"name": "sleep_worst_since_x",
"period": 1,
"priority": 2,
"attribute": {
"name": "sleep",
"label": "Time asleep",
"group": {
"name": "sleep",
"priority": 3
},
"priority": 2
}
}
}
]
}
Returns a paged list of user’s insights for a specific attribute. Only available for the currently authenticated user.
Request
GET /api/1/users/:username/insights/attribute/:attribute/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return per page, starting with today. Optional, max is 100. |
page |
Page index. Optional, default is 1. |
date_min |
Oldest date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
date_max |
Most recent date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
Averages
Get current averages
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/averages/
import requests
requests.get("https://exist.io/api/1/users/$self/averages/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a JSON array:
[
{
"attribute": "steps",
"date": "2020-04-29",
"overall": 4174.0,
"monday": 4057.0,
"tuesday": 6614.0,
"wednesday": 4001.0,
"thursday": 3923.0,
"friday": 4528.0,
"saturday": 3649.0,
"sunday": 3904.0
},
{
"attribute": "floors",
"date": "2020-04-29",
"overall": 13.0,
"monday": 13.0,
"tuesday": 16.0,
"wednesday": 14.0,
"thursday": 12.0,
"friday": 14.0,
"saturday": 13.0,
"sunday": 12.0
}
]
Returns the most recent average values for each attribute. Only available for the currently authenticated user.
Request
GET /api/1/users/:username/averages/
Get all averages for attribute
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/averages/attribute/steps/
import requests
requests.get("https://exist.io/api/1/users/$self/averages/attribute/steps/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a JSON object containing a paged array of results:
{
"count": 11,
"next": null,
"previous": null,
"results": [
{
"attribute": "steps",
"date": "2020-04-29",
"overall": 4174.0,
"monday": 4057.0,
"tuesday": 6614.0,
"wednesday": 4001.0,
"thursday": 3923.0,
"friday": 4528.0,
"saturday": 3649.0,
"sunday": 3904.0
},
{
"attribute": "steps",
"date": "2020-03-30",
"overall": 4062.0,
"monday": 4057.0,
"tuesday": 6618.0,
"wednesday": 3610.0,
"thursday": 3923.0,
"friday": 4063.0,
"saturday": 3636.0,
"sunday": 3904.0
}
]
}
Returns a paged list of average values for an attribute.
Request
GET /api/1/users/:username/averages/attribute/:attribute/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return per page, starting with today. Optional, max is 100. |
page |
Page index. Optional, default is 1. |
date_min |
Oldest date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
date_max |
Most recent date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
Correlations
Get all correlations for attribute
curl -H "Authorization: Token [YOUR_TOKEN]" https://exist.io/api/1/users/\$self/correlations/attribute/steps/
import requests
requests.get("https://exist.io/api/1/users/$self/correlations/attribute/steps/",
headers={'Authorization':'Token [YOUR_TOKEN]'})
Returns a JSON object containing an array of results:
{
"count": 479,
"next": "https://exist.io/api/1/users/josh/correlations/attribute/steps/?page=2",
"previous": null,
"results": [
{
"date": "2015-05-11",
"period": 90,
"attribute": "steps",
"attribute2": "steps_distance",
"value": 0.999735821732415,
"p": 5.43055953485446e-146,
"first_person": "I am almost certain (100%) to travel a further distance on days I take more steps.",
"second_person": "You are almost certain (100%) to travel a further distance on days you take more steps."
},
{
"date": "2015-05-11",
"period": 90,
"attribute": "steps",
"attribute2": "floors",
"value": 0.693332839316818,
"p": 3.63477333939521e-14,
"first_person": "I am much more likely (69%) to climb floors on days I take more steps.",
"second_person": "You are much more likely (69%) to climb floors on days you take more steps."
}
]
}
Returns a paged list of all correlations generated relating to this attribute, ordered by date. Correlations may appear more than once, with different results, as this relationship changes over time.
Request
GET /api/1/users/:username/correlations/attribute/:attribute/
Parameters
| Name | Description |
|---|---|
limit |
Number of values to return per page, starting with today. Optional, max is 100. |
page |
Page index. Optional, default is 1. |
date_min |
Oldest date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
date_max |
Most recent date (inclusive) of results to be returned, in format YYYY-mm-dd. Optional. |
latest |
Set this to true to return only the most recently generated batch of correlations. Use this on its own without date_min and date_max. |
Attribute ownership
This section only applies for OAuth2 clients.
Only one service may have ownership of any user attribute at any given time. Services must acquire ownership of an attribute to be able to write data for this attribute, and can release ownership if needed, for example if the user closes their account with this service or chooses to turn off certain attributes.
Acquire attributes
curl https://exist.io/api/1/attributes/acquire/ -H "Content-Type: application/json" -H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737" -X POST -d '[{"name":"mood", "active":true}, {"name":"mood_note", "active":true}]'
import requests, json
url = 'https://exist.io/api/1/attributes/acquire/'
attributes = [{"name":"mood", "active":True}, {"name":"mood_note", "active":True}]
response = requests.post(url, headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'},
data=json.dumps(attributes))
Returns JSON and a status code of
202 Acceptedif some attributes failed (just for example, the above is correct)
{ "success": [
{ "name":"mood_note",
"active":"true"
}
],
"failed": [
{ "name":"mood",
"error_code":"missing_field",
"error":"Object at index 0 missing field(s) 'active'"
}
]
}
Allows a service to update attribute data for these attributes. Users do not have to approve this (mostly because this would be cumbersome) so please explain/confirm this behaviour with users within your own application.
Request
POST /api/1/attributes/acquire/
Parameters
Clients must send a JSON-encoded array of objects, where each object contains a name string and an active boolean. Setting active to false indicates you’d like to deactivate this attribute without giving up ownership.
| Name | Description |
|---|---|
name |
The attribute name, eg. mood_note |
active |
true or false to set this attribute to active or inactive |
private |
Optional true or false to change the privacy status of this attribute. Please notify users if you are making previously private attributes public and only do this with good reason. |
Response
Returns 200 OK if all attributes were processed successfully, or 202 Accepted if some attributes failed. The content is a JSON object containing success and failed arrays, where each item in the array is an attribute sent in the prior request. Failed attributes get error and error_code fields added.
Release attributes
curl https://exist.io/api/1/attributes/release/ -H "Content-Type: application/json" -H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737" -X POST -d '[{"name":"mood"}, {"name":"mood_note"}]'
import requests, json
url = 'https://exist.io/api/1/attributes/release/'
attributes = [{"name":"mood"}, {"name":"mood_note"}]
response = requests.post(url, headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'},
data=json.dumps(attributes))
Returns JSON and a status code of
202 Acceptedif some attributes failed (just for example, the above is correct)
{ "success": [
{ "name":"mood_note" }
],
"failed": [
{ "name":"mood",
"error_code":"unauthorised",
"error":"Attribute 'mood' does not belong to this service"
}
]
}
Do this to release your ownership of any attributes. The attributes’ ownership will pass to another service, if the user has another supplied that has indicated it can handle this attribute, or otherwise become inactive.
Request
POST /api/1/attributes/release/
Parameters
Clients must send a JSON-encoded array of objects, where each object contains a name string. The objects may seem superfluous but this is to be consistent with the acquire endpoint.
| Name | Description |
|---|---|
name |
The attribute name, eg. mood_note |
Response
Returns 200 OK if all attributes were processed successfully, or 202 Accepted if some attributes failed. The content is a JSON object containing success and failed arrays, where each item in the array is an attribute sent in the prior request. Failed attributes get error and error_code fields added.
List owned attributes
curl https://exist.io/api/1/attributes/owned/ -H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737"
import requests
url = 'https://exist.io/api/1/attributes/owned/'
response = requests.get(url, headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'})
Returns a JSON array of attributes for the authenticated user and owned by this service:
[
{
"attribute": "steps",
"label": "Steps",
"value": null,
"service": "fitbit",
"priority": 1,
"private": false,
"value_type": 0,
"value_type_description": "Integer"
},
{
"attribute": "steps_active_min",
"label": "Active minutes",
"value": null,
"service": "fitbit",
"priority": 2,
"private": false,
"value_type": 0,
"value_type_description": "Integer"
}
]
This is a convenience endpoint to list all attributes for the authenticated user currently owned by this service.
Request
GET /api/1/attributes/owned/
Updating attributes
Overview
If you jumped straight here looking for how to send data for your own attributes, here’s a recap of the steps you may have missed:
- Make sure you have an OAuth2 client set up
- Make sure you’ve listed all attributes you’d like to be able to write to (set this from your app management page)
- Make sure you have acquired ownership of the attribute before updating
- Now you can update.
Updating attribute values
curl https://exist.io/api/1/attributes/update/ -H "Content-Type: application/json" -H "Authorization: Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737" -X POST -d '[{"name":"mood", "date":"2015-05-20", "value":5}, {"name":"mood_note", "date":"2015-05-20", "value":"Great day playing with the Exist API"}]'
python
import requests, json
url = 'https://exist.io/api/1/attributes/update/'
attributes = [{"name":"mood", "date":"2015-05-20", "value":5}, {"name":"mood_note", "date":"2015-05-20", "value":"Great day playing with the Exist API"}]
response = requests.post(url, headers={'Authorization':'Bearer 96524c5ca126d87eb18ee7eff408ca0e71e94737'},
data=json.dumps(attributes))
Returns a JSON object containing successful and failed updates:
{ "success": [
{ "name":"mood_note",
"date":"2015-05-20",
"value":"Great day playing with the Exist API"
}
],
"failed": [
{ "name":"mood",
"date":"2015-05-20",
"error_code":"missing_field",
"error":"Object at index 0 missing field(s) 'value'"
}
]
}
This section only applies for OAuth2 clients.
This endpoint allows services to update attribute data for the authenticated user. Data is stored on a single day granularity, so each update contains name, date, and value. Make sure the date is local to the user — though you do not have to worry about timezones directly, if you are using your local time instead of the user’s local time, you may be a day ahead or behind!
Valid values are described by the attribute’s value_type and value_type_description fields. However, values are only validated broadly by type and so care must be taken to send correct data. Do not rely on Exist to validate your values beyond
enforcing the correct type.
Check value types for each attribute in list of supported attributes.
Request
POST /api/1/attributes/update/
Parameters
Clients must send a JSON-encoded array of objects containing a name, date, and value.
| Name | Description |
|---|---|
name |
The attribute name, eg. mood_note |
date |
String of format YYYY-mm-dd |
value |
A valid value for this attribute type: string, integer, or float |
Response
Returns 200 OK if all attributes were processed successfully, or 202 Accepted if some attributes failed. The content is a JSON object containing success and failed arrays, where each item in the array is an attribute sent in the prior request. Failed attributes get error and error_code fields added.
API roadmap
Upcoming
Our main ongoing priority is extending the list of supported attributes so users can track a wider range of things. Please let us know if you’d like to see a certain attribute or data type supported.
Changelog
- 2015-12-01: OAuth2 clients are available for all users
- 2015-10-05: Removed separate value fields in insights, added rendered text field
- 2015-07-09: Introduced attribute validation and started validating
moodvalues - 2015-05-13: Added date filtering for insights, correlations, averages, and attribute data.
Questions and feedback
You can always send us an email with questions.
Please, give the API a thorough workout and tell us what’s inconsistent or missing. Your feedback is going to help us shape a sensible, robust API v1 that we’ll collectively be stuck with for a fair while.
Sign up to the developer list
The easiest way to be kept up to date about API changes and announcements is to join the list.