Overview

The Discovery API is a read-only batch API that allow developers to get lists of Mode resources, along with metadata about each of those resources. The API will be referred to as ‘batch’ throughout the documentation. The resources currently supported are Reports, Spaces, Queries and Charts.

Note: To create a valid Signature Token for accessing the Discovery API, an organization admin’s api key & secret are required for authenticating the token creation endpoint. The organization must have an active, paid plus subscription. All other endpoints will require a valid Batch API Signature Token for access.

Authentication & Authorization

To return a valid signature token (to then be used for Batch API requests) send a POST request. signature_token should be a top level key in the raw data sent.

  • name is required to be a string between 4-64 characters long
  • expires_at must be in ISO8601 (YYYY-MM-DDTHH:mmZ) format
  • auth_scope must be a JSON object.
    • authentication_for and authorization_type are the only valid keys.
      • batch-api is the only valid value for authentication_for.
      • read-only is the only valid value for authorization_type.

Example POST body:

"signature_token": {
    "name": "token-name-here",
    "expires_at": "2020-08-18T22:58:47+0000",
    "auth_scope": { "authentication_for" : "batch-api",
                    "authorization_type": "read-only" }
}

Getting a Signature Token for Authentication

Note: Replace {account} with your organization’s username.

Request Examples:

curl --location --request POST 'https://app.mode.com/batch/{account}/signature_tokens' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--user 'your-api-key':'your-api-secret' \
--data-raw '{
    "signature_token": {
        "name": "batch-api-token",
        "expires_at": "2020-10-02T21:00:00+0000",
        "auth_scope": { "authentication_for" : "batch-api", "authorization_type": "read-only" }
    }
}';
require 'http'

username = 'your_api_key'
password = 'your_api_secret'

headers = {
  content_type: 'application/json',
  accept: 'application/json'
}

form_data = {
  "signature_token": {
    "name": "batch-api-token",
    "expires_at": "2020-10-02T21:00:00+0000",
    "auth_scope": {
      "authentication_for": "batch-api",
      "authorization_type": "read-only"
    }
  }
}

response = HTTP.basic_auth(user: username, pass: password)
               .headers(headers)
               .post('https://app.mode.com/batch/{account}/signature_tokens', json: form_data)
puts response
from urllib2 import Request, urlopen
from base64 import b64encode
from json import dumps

username = 'your-api-key'
password = 'your-api-secret'
basic_auth_creds = b64encode('%s:%s' % (username, password))

form_data = {
  "signature_token": {
    "name": "batch-api-token",
    "expires_at": "2020-10-02T21:00:00+0000",
    "auth_scope": {
      "authentication_for": "batch-api",
      "authorization_type": "read-only"
    }
  }
}

request = Request('https://app.mode.com/batch/{account}/signature_tokens')

request.add_header("Authorization", "Basic %s" % basic_auth_creds)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")

response = urlopen(request, dumps(form_data))
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();
var basic_auth = 'Basic ' + btoa('your-api-key:your-api-secret');


var form_data = {
  "signature_token": {
    "name": "batch-api-token",
    "expires_at": "2020-10-02T21:00:00+0000",
    "auth_scope": {
      "authentication_for": "batch-api",
      "authorization_type": "read-only"
    }
  }
};

request.open('POST', 'https://app.mode.com/batch/{account}/signature_tokens');
request.withCredentials = true;

request.setRequestHeader('Authorization', basic_auth);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send(JSON.stringify(form_data));

Example Response:

{
  "token": "5b89bf4b0a49",
  "name": "batch-api-token-1",
  "creator_id": 3,
  "organization_id": 4,
  "access_key": "8b6fdc0a36bf340604a3cedc",
  "access_secret": "829146cb6c752f51bbbb8c85",
  "created_at": "2020-07-02T21:01:17.181Z",
  "updated_at": "2020-07-02T21:01:17.181Z",
  "expires_at": "2020-10-02T21:00:00.000Z",
  "authentication_for": "batch-api",
  "authorization_type": "read-only"
}

Deleting a Signature Token

Note: Replace {account} with your org’s username. Replace {token} with the Signature Token’s token (the 12-character alphanumeric identifier).

Request Examples:

curl --location --request DELETE 'http://goat.farm:3000/batch/{account}/signature_tokens/{token}' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --user 'your-api-key':'your-api-secret' \
require 'http'

username = 'your_api_key'
password = 'your_api_secret'

headers = {
  content_type: 'application/json',
  accept: 'application/json'
}

response = HTTP.basic_auth(user: username, pass: password)
               .headers(headers)
               .delete('https://app.mode.com/batch/{account}/signature_tokens/{token}')
puts response
from urllib2 import Request, urlopen
from base64 import b64encode

username = 'your-api-key'
password = 'your-api-secret'
basic_auth_creds = b64encode('%s:%s' % (username, password))

request = Request('https://app.mode.com/batch/{account}/signature_tokens/{token}')

request.add_header("Authorization", "Basic %s" % basic_auth_creds)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")
request.get_method = lambda: 'DELETE'

response = urlopen(request)
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();
var basic_auth = 'Basic ' + btoa('your-api-key:your-api-secret');

request.open('DELETE', 'https://app.mode.com/batch/{account}/signature_tokens/{token}');
request.withCredentials = true;

request.setRequestHeader('Authorization', basic_auth);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send();

Example Response:

{
  "message": "That signature token has been deleted"
}

Using the Signature Token to Authenticate Requests

signature_token = {
  "token": "5b89bf4b0a49",
  "name": "batch-api-token-1",
  "creator_id": 3,
  "organization_id": 4,
  "access_key": "8b6fdc0a36bf340604a3cedc",
  "access_secret": "829146cb6c752f51bbbb8c85",
  "created_at": "2020-07-02T21:01:17.181Z",
  "updated_at": "2020-07-02T21:01:17.181Z",
  "expires_at": "2020-10-02T21:00:00.000Z",
  "authentication_for": "batch-api",
  "authorization_type": "read-only"
}

id            = signature_token["token"]
access_key    = signature_token["access_key"]
access_secret = signature_token["access_secret"]
decoded_token = "#{id}:#{access_key}:#{access_secret}"
encoded_token = Base64.strict_encode64(decoded_token)
bearer_token  = "Bearer #{encoded_token}"
import base64;

decoded_token = id + ':' + access_key + ':' + access_secret
encoded_token = base64.b64encode(decoded_token)
bearer_token  = 'Bearer ' + encoded_token
decoded_token = id + ':' + access_key + ':' + access_secret;
encoded_token = btoa(decoded_token);
bearer_token  = 'Bearer ' + encoded_token;

List Charts for an Organization

To return a paginated list of all charts for a given Organization, send a GET request. Each page has 1000 charts.

Request Examples:

curl --location --request GET 'http://app.mode.com/batch/{account}/charts' \
     --header "Content-Type: application/json" \
     --header "Accept: application/json" \
     --header "Authorization: {bearer_token}"
require 'http'

decoded_token = "#{id}:#{access_key}:#{access_secret}"
encoded_token = Base64.strict_encode64(decoded_token)
bearer_token  = "Bearer #{encoded_token}"

headers = {
  content_type: 'application/json',
  accept: 'application/json',
  authorization: bearer_token
}

response = HTTP.headers(headers)
               .get('https://app.mode.com/batch/{account}/charts')
puts response
from urllib2 import Request, urlopen
from base64 import b64encode

decoded_token = id + ':' + access_key + ':' + access_secret
encoded_token = b64encode(decoded_token)
bearer_token  = 'Bearer ' + encoded_token

request = Request("http://app.mode.com/batch/{account}/charts")

request.add_header("Authorization", bearer_token)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")

response = urlopen(request)
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();

var decoded_token = id + ':' + access_key + ':' + access_secret;
var encoded_token = btoa(decoded_token);
var bearer_token  = 'Bearer ' + encoded_token;

request.open('GET', 'https://app.mode.com/batch/{account}/charts');
request.withCredentials = true;

request.setRequestHeader('Authorization', bearer_token);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send();

Example Response:

{
    "pagination": {
        "page": 1,
        "per_page": 1000,
        "count": 1000,
        "total_pages": 8,
        "total_count": 7335
    },
    "charts": [
        {
            "id": 172,
            "token": "728f6328ea67",
            "chart_type": "stackedBar100",
            "chart_title": "chart no. 0",
            "query_id": 170,
            "report_id": 5244,
            "space_id": 8,
            "query_token": "e84d267326af",
            "report_token": "3d309a5998c8",
            "space_token": "ece07661108c"
        },
        {
            "id": 4541,
            "token": "bfe4fc361d9f",
            "chart_type": "hStackedBar",
            "chart_title": "Et repellat voluptas",
            "query_id": 170,
            "report_id": 5244,
            "space_id": 8,
            "query_token": "14b73995877a",
            "report_token": "661c6ab667e0",
            "space_token": "ece07661108c"
        },
        {
            "id": 4542,
            "token": "25e6637df704",
            "chart_type": "stackedBar",
            "chart_title": "Autem voluptas velit",
            "query_id": 170,
            "report_id": 5244,
            "space_id": 8,
            "query_token": "38573c120c75",
            "report_token": "cdc1ef078e96",
            "space_token": "ece07661108c"
        },
        ...
    ],
    "_links": {
        "next_page": {
            "href": "/batch/{account}/charts?page=2&per_page=1000"
        },
        "prev_page": {
            "href": "/batch/{account}/charts?page=1&per_page=1000"
        }
    }
}

List Queries for an Organization

To return a paginated list of all queries for a given Organization, send a GET request. Each page has 1000 queries.

Request Examples:

curl --location --request GET 'http://app.mode.com/batch/{account}/queries' \
     --header "Content-Type: application/json" \
     --header "Accept: application/json" \
     --header "Authorization: {bearer_token}"
require 'http'

decoded_token = "#{id}:#{access_key}:#{access_secret}"
encoded_token = Base64.strict_encode64(decoded_token)
bearer_token  = "Bearer #{encoded_token}"

headers = {
  content_type: 'application/json',
  accept: 'application/json',
  authorization: bearer_token
}

response = HTTP.headers(headers)
               .get('https://app.mode.com/batch/{account}/queries')
puts response
from urllib2 import Request, urlopen
from base64 import b64encode

decoded_token = id + ':' + access_key + ':' + access_secret
encoded_token = b64encode(decoded_token)
bearer_token  = 'Bearer ' + encoded_token

request = Request("http://app.mode.com/batch/{account}/queries")

request.add_header("Authorization", bearer_token)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")

response = urlopen(request)
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();

var decoded_token = id + ':' + access_key + ':' + access_secret;
var encoded_token = btoa(decoded_token);
var bearer_token  = 'Bearer ' + encoded_token;

request.open('GET', 'https://app.mode.com/batch/{account}/queries');
request.withCredentials = true;

request.setRequestHeader('Authorization', bearer_token);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send();

Example Response:

{
    "pagination": {
        "page": 1,
        "per_page": 1000,
        "count": 1000,
        "total_pages": 5,
        "total_count": 4146
    },
    "queries": [
        {
            "id": 3022,
            "token": "25e6637df704",
            "name": "Ut qui",
            "raw_query": "\n      SELECT DISTINCT code, name\n        FROM states\n\n      {% form %}\n        state:\n          type: select\n          default: 'GA'\n          options:\n            values: code\n            labels: name\n      {% endform %}\n    ",
            "report_id": 10850,
            "report_token": "602797d3122f"
        },
        {
            "id": 3005,
            "token": "bfe4fc361d9f",
            "name": "Aspernatur",
            "raw_query": "\n      SELECT DISTINCT code, name\n        FROM states\n\n      {% form %}\n        state:\n          type: select\n          default: 'GA'\n          options:\n            values: code\n            labels: name\n      {% endform %}\n    ",
            "report_id": 10833,
            "report_token": "712489649deb"
        },
        {
            "id": 3014,
            "token": "728f6328ea67",
            "name": "Et sint",
            "raw_query": "\n      SELECT DISTINCT code, name\n        FROM states\n\n      {% form %}\n        state:\n          type: select\n          default: 'GA'\n          options:\n            values: code\n            labels: name\n      {% endform %}\n    ",
            "report_id": 10842,
            "report_token": "08922fac4d74"
        },
        ...
    ],
    "_links": {
        "next_page": {
            "href": "/batch/{account}/queries?page=2&per_page=1000"
        },
        "prev_page": {
            "href": "/batch/{account}/queries?page=1&per_page=1000"
        }
    }
}

List Reports for an Organization

To return a paginated list of all reports for a given Organization, send a GET request. Each page has 1000 reports.

Request Examples:

curl --location --request GET 'http://app.mode.com/batch/{account}/reports' \
     --header "Content-Type: application/json" \
     --header "Accept: application/json" \
     --header "Authorization: {bearer_token}"
require 'http'

decoded_token = "#{id}:#{access_key}:#{access_secret}"
encoded_token = Base64.strict_encode64(decoded_token)
bearer_token  = "Bearer #{encoded_token}"

headers = {
  content_type: 'application/json',
  accept: 'application/json',
  authorization: bearer_token
}

response = HTTP.headers(headers)
               .get('https://app.mode.com/batch/{account}/reports')
puts response
from urllib2 import Request, urlopen
from base64 import b64encode

decoded_token = id + ':' + access_key + ':' + access_secret
encoded_token = b64encode(decoded_token)
bearer_token  = 'Bearer ' + encoded_token

request = Request("http://app.mode.com/batch/{account}/reports")

request.add_header("Authorization", bearer_token)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")

response = urlopen(request)
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();

var decoded_token = id + ':' + access_key + ':' + access_secret;
var encoded_token = btoa(decoded_token);
var bearer_token  = 'Bearer ' + encoded_token;

request.open('GET', 'https://app.mode.com/batch/{account}/reports');
request.withCredentials = true;

request.setRequestHeader('Authorization', bearer_token);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send();

Example Response:

{
    "pagination": {
        "page": 1,
        "per_page": 1000,
        "count": 1000,
        "total_pages": 5,
        "total_count": 4146
    },
     "reports": [
        {
            "id": 5244,
            "token": "728f6328ea67",
            "name": null,
            "description": null,
            "created_at": "2020-05-04T20:17:55.387Z",
            "edited_at": null,
            "updated_at": "2020-05-04T20:17:55.387Z",
            "last_successfully_run_at": null,
            "report_url": "/api/modeanalytics/reports/728f6328ea67"
        },
        {
            "id": 5412,
            "token": "bfe4fc361d9f",
            "name": null,
            "description": null,
            "created_at": "2020-05-04T20:17:57.253Z",
            "edited_at": null,
            "updated_at": "2020-05-04T20:17:57.253Z",
            "last_successfully_run_at": null,
            "report_url": "/api/modeanalytics/reports/bfe4fc361d9f"
        },
        ...
    ],
    "_links": {
        "next_page": {
            "href": "/batch/{account}/reports?page=2&per_page=1000"
        },
        "prev_page": {
            "href": "/batch/{account}/reports?page=1&per_page=1000"
        }
    }
}

List Spaces for an Organization

To return a paginated list of all spaces for a given Organization, send a GET request. Each page has 1000 spaces.

Request Examples:

curl --location --request GET 'http://app.mode.com/batch/{account}/spaces' \
     --header "Content-Type: application/json" \
     --header "Accept: application/json" \
     --header "Authorization: {bearer_token}"
require 'http'

decoded_token = "#{id}:#{access_key}:#{access_secret}"
encoded_token = Base64.strict_encode64(decoded_token)
bearer_token  = "Bearer #{encoded_token}"

headers = {
  content_type: 'application/json',
  accept: 'application/json',
  authorization: bearer_token
}

response = HTTP.headers(headers)
               .get('https://app.mode.com/batch/{account}/spaces')
puts response
from urllib2 import Request, urlopen
from base64 import b64encode

decoded_token = id + ':' + access_key + ':' + access_secret
encoded_token = b64encode(decoded_token)
bearer_token  = 'Bearer ' + encoded_token

request = Request("http://app.mode.com/batch/{account}/spaces")

request.add_header("Authorization", bearer_token)
request.add_header("Accept", "application/json")
request.add_header("Content-Type", "application/json")

response = urlopen(request)
response_body = response.read()

print(response_body)
var request = new XMLHttpRequest();

var decoded_token = id + ':' + access_key + ':' + access_secret;
var encoded_token = btoa(decoded_token);
var bearer_token  = 'Bearer ' + encoded_token;

request.open('GET', 'https://app.mode.com/batch/{account}/spaces');
request.withCredentials = true;

request.setRequestHeader('Authorization', bearer_token);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Accept', 'application/json');

request.onreadystatechange = function () {
  if (this.readyState === 4) {
    console.log('Status:', this.status);
    console.log('Headers:', this.getAllResponseHeaders());
    console.log('Body:', this.responseText);
  }
};

request.send();

Example Response:

{
    "pagination": {
        "page": 1,
        "per_page": 1000,
        "count": 1000,
        "total_pages": 5,
        "total_count": 4146
    },
     "spaces": [
        {
            "id": 170,
            "token": "728f6328ea67",
            "name": null,
            "description": null,
            "space_url": '/api/modeanalytics/spaces/728f6328ea67'
        },
        {
            "id": 171,
            "token": "25e6637df704",
            "name": null,
            "description": null,
            "space_url": '/api/modeanalytics/spaces/25e6637df704'
        },
        ...
    ],
    "_links": {
        "next_page": {
            "href": "/batch/{account}/spaces?page=2&per_page=1000"
        },
        "prev_page": {
            "href": "/batch/{account}/spaces?page=1&per_page=1000"
        }
    }
}

Last updated July 27, 2020