Authentication

# Set these environment variables
$ export PROJECT_ID=PROJECT_ID
$ export MASTER_KEY=MASTER_KEY
$ export READ_KEY=READ_KEY
$ export WRITE_KEY=WRITE_KEY
$ export EVENT_NAME=EVENT_NAME

Keen supports two mechanisms for authenticating API requests, both requiring API Keys for the project you want to use.

HTTP Header

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/events/purchases \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "key": 123
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY',
  writeKey: 'WRITE_KEY'
});

One way you can authenticate requests is with an HTTP header called “Authorization”. This method works for every API call.

Query String Parameter

Request

$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/events/purchases?api_key=MASTER_KEY&key=123"

You can also authenticate requests with a query string parameter called api_key.

API Keys

Example

import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY',
  writeKey: 'WRITE_KEY'
});
keen_project = Keen::Client.new(
    :project_id => "your_project_id",
    :write_key => "your_project_write_key",
    :read_key => "your_project_read_key",
)
from keen.client import KeenClient

client = KeenClient(
    project_id="your_project_id",
    write_key="your_project_write_key",
    read_key="your_project_read_key"
)
<?php

use KeenIO\Client\KeenIOClient;

$client = KeenIOClient::factory([
    'projectId' => $projectId,
    'writeKey'  => $writeKey,
    'readKey'   => $readKey
]);

?>
#import <KeenClient/KeenClient.h> // This import works for a framework version of KeenClient (CocoaPods with use_frameworks! or Carthage).
// or
#import "KeenClient.h" // If linking against a static library version (CocoaPods without use_frameworks!)
// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [KeenClient sharedClientWithProjectID:@"your_project_id" andWriteKey:@"your_write_key" andReadKey:@"your_read_key"];
    return YES;
}
KeenClient client = new JavaKeenClientBuilder().build();
KeenProject project = new KeenProject(PROJECT_ID, WRITE_KEY, READ_KEY);
client.setDefaultProject(project);

// for Android
KeenClient client = new AndroidKeenClientBuilder(this).build();
KeenProject project = new KeenProject(PROJECT_ID, WRITE_KEY, READ_KEY);
client.setDefaultProject(project);
var prjSettings = new ProjectSettingsProvider("YourProjectID", writeKey: "YourWriteKey");
var keenClient = new KeenClient(prjSettings);

Each of your projects will have its own set of API Keys, which you can retrieve from the overview page of your project.

  1. Go to https://keen.io/project/PROJECT_ID
  2. Click the “Access” tab
  3. Select and copy the appropriate key

Master Key

import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

The Master Key is the most powerful API Key of all. It can be used to authenticate any API call, and is required to perform various administrative functions, such as:

Write Key

import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

The Write Key is an API Key specifically for writing data. It can authenticate any API request that writes data to Keen IO. The Write Key is automatically created for each project, and can be regenerated. Typically, you would use the Write Key for requests described in the Record section of the docs.

Read Key

import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

The Read Key is an API Key for querying and extracting data. It can authenticate any API request to query or analyze data from Keen IO. Like the Write Key, the Read Key is automatically created for each project, and can be regenerated. Typically, you would use the Read Key for requests described in the Query section of the docs.

Organization Key

The Organization Key is an API Key for programmatically managing your Keen IO account information. When an organization is created, it will be assigned an Organization Key automatically.

Use the Organization Key for requests described in the Access section of the docs.

Access Key

An Access Key is an API Key generated by the API to identify the source or user making a request to the Keen IO.

You can programmatically generate, revoke, or modify Access Keys. For example, if you wanted to have customer-facing analytics in your app, Access Keys would allow individual customers to see their own data without exposing anyone else’s data. Access Keys can also restrict where a user can send data or automatically include other data properties.

Each key has a defined scope of permitted operations. You can read more on how to create, revoke, or modify Access Keys in the full Access Keys section.

Limits

Our #1 job here at Keen IO is to maintain the reliability and stability of our API. As you might expect, we’ve put a number of protections in place to safeguard the service.

The limits documented here ensure “out of the box” service levels you should expect, however many of these limits can be tuned by project. Contact us to discuss custom limits for your project!

Rate Limiting

Request Type Request Limits
Recording Events No Limit!
Ad-Hoc Queries 200/minute — email support to have this raised
Extractions 200/minute — email support to have this raised
Deletes 10/minute — email support to have this raised

How Rate Limits Work

  • If too many requests are made in a minute, rate limiting kicks in and all requests of that type will be blocked for the next minute.
  • If the number of requests goes back down under the limit, the block is lifted.
  • Blocked requests return a 429 with a message about the limit.
  • Being blocked in one rate limit category does not affect others.
  • Limits are enforced at the project level.
  • Cached Queries and Cached Datasets pre-computations do not contribute to ad-hoc query rate limiting. This combined with subsecond response times makes them ideal for building customer-facing embedded analytics!

Concurrency Limiting

Concurrency limits restrict the number of queries you may have executing simultaneously, with a separate limit for the number you may have awaiting execution.

Keen IO employs per Organization fuzzy concurrency limits for queries based on the workload we are facing. These limits are sometimes adjusted if the platform is under duress or if there are other extenuating circumstances.

In these situations the API may will return a 429.

Fast Failure

Keen strives to begin executing your query promptly. Unfortunately sometimes the number of queries may exceed our capacity and to prevent poor experiences we will “fail fast” your queries. Queries that are not promptly executed will return a 503 status code.

Event Sizes

Individual events are limited to 900,000 bytes. Bulk event payloads are limited to 10,000,000 bytes.

Extraction Limits

Request Type Limit
Synchronous Extraction 1,000,000 events scanned; 100,000 events extracted
Asynchronous Extraction 10,000,000 events

The extraction request type allows you to pull your raw event data out of Keen IO. If you need to extract more than the limit, break up your request into chunks by timeframe. For example, if a month’s worth of data is over 10M events, you could run one extraction for each week.

Other Query Limits

Request Type Limit
Count Unique Approximation kicks in it at 1,000,000 distinct values
Group By 1,000,000 unique groups
Max Intervals 9,000 intervals
Median Approximation kicks in it at 1,000,000 distinct values
Percentile Approximation kicks in it at 1,000,000 distinct values
Funnel 1,000,000 unique actors

Cached Query Limits

Cached Queries must have a refresh_rate greater than 4 hours, and less than 24 hours.

Timeouts

Request Type Limit
Max Query Response Time 5 minutes

The API will automatically end any query that takes longer than 5 minutes to run. Queries will return a 504 response code with the response message seen on the right.

Response

{
  "message": "Your query did not finish in 300 seconds. Most likely something is wrong on our side. Please let us know at team@keen.io.",
  "error_code": "QueryIncompleteError"
}

The most common reason for a long-running query is simply that the query touches a lot of data points.

How to Reduce Timeouts and Improve Query Performance

Because Keen IO indexes on timestamps, the easiest way to improve query speed is to reduce the timeframe of the query, or split it into smaller component chunks. A query with the timeframe “last_week” will run several times faster than a query with the timeframe “last month”. Query time grows approximately linearly as the number of datapoints your timeframe spans increases.

Errors

200: Event accepted

Note: Check the response body for individual event statuses. Some or all may have failed.

201: Event created successfully

400: Bad Request

Missing or invalid parameters. Error messages will often contain more information to help you figure out what’s wrong.

401: Unauthorized

The API Key is invalid.

403: Forbidden

The API Key is not allowed to run this request.

404: Not Found

The requested resource was not found.

429: Too many requests, see Rate Limiting

500: Internal Server Error

Something went wrong on our end. If the issue persists, give us a shout so we can investigate.

503: Service Unavailable

Our service is temporarily offline. If the issue persists, let us know.

504 - Timeout

Your query did not complete in the time allowed, see Timeouts.

Versions

Request

$ curl "https://api.keen.io/?api_key=MASTER_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('base'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle errors
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

[
  {
    "is_public": true,
    "url": "/3.0",
    "version": "3.0"
  },
  {
    "is_public": false,
    "url": "/2.0",
    "version": "2.0"
  },
  {
    "is_public": false,
    "url": "/1.0",
    "version": "1.0"
  }
]

Returns all available API versions.

HTTP Methods

Method Authentication Response
GET Master Key All available API versions
HEAD Master Key Response header

Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

A note about versioning

This API reference guide is based solely on the current version of our API (3.0), so all subsequent resources will stem from “/3.0”, like so:

https://api.keen.io/3.0/

Streams

Events

Resource

https://api.keen.io/3.0/projects/PROJECT_ID/events

Events are the individual data points collected by the Keen API. These events are stored in collections, which you can access through the Events resource. This flexible resource allows you to operate on multiple event collections for a given project with a single request. The specific actions for GET and POST requests are described in detail below.

Record a single event

Request

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "key": 123
    }'

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=WRITE_KEY&data=ENCODED_DATA"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const purchaseEvent = {
  item: 'golden gadget',
  price: 25.50,
  referrer: document.referrer,
  keen: {
    timestamp: new Date().toISOString()
  }
};

client.recordEvent('purchases', purchaseEvent, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});
Keen.publish(:sign_ups, { :username => "lloyd", :referred_by => "harry" })
keen.add_event("sign_ups", {
  "username": "lloyd",
  "referred_by": "harry"
})
<?php

$event = ['username' => "lloyd" , "referred_by" => "harry"];

$client->addEvent('sign_ups', $event);

?>
// create an event
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys:@"first view", @"view_name", @"going to", @"action", nil];
    [[KeenClient sharedClient] addEvent:event toEventCollection:@"tab_views" error:nil];
}

// upload event
- (void)applicationDidEnterBackground:(UIApplication *)application
{
  UIBackgroundTaskIdentifier taskId = [application beginBackgroundTaskWithExpirationHandler:^(void) {
      NSLog(@"Background task is being expired.");
  }];

  [[KeenClient sharedClient] uploadWithFinishedBlock:^(void) {
      [application endBackgroundTask:taskId];
  }];
}
protected void track() {
  // Create an event to upload to Keen.
  Map<String, Object> event = new HashMap<String, Object>();
  event.put("item", "golden widget");

  // Add it to the "purchases" collection in your Keen Project.
  KeenClient.client().addEvent("purchases", event);
}
var purchase = new
{
    category = "magical animals",
    username = "hagrid",
    price = 7.13,
    payment_type = "information",
    animal_type = "norwegian ridgeback dragon"
};

keenClient.AddEvent("purchases", purchase);

Response

{
  "created": true
}

A POST or GET request authenticated with a Write Key records a single event to a given event collection.

Supported HTTP Methods

Method Authentication Description
GET Write Key Record a single event
POST Write Key Record a single event

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header
data URL-encoded AND base-64 encoded event body
redirect Optional: A URL to redirect the request toward after the event is recorded. If the protocol (http:// or https://) is omitted, we will automatically include one.

Image Beacons

Example Image Beacon

<img src="https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=WRITE_KEY&data=ENCODED_DATA"/>

Set a GET request URL (with a data parameter containing a URL/base-64 encoded event body payload) as the src attribute of an HTML image tag. When the image “loads” the event will be recorded.

Read more about Image Beacons

URL Redirects

Example Redirect

<a href="https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=WRITE_KEY&data=ENCODED_DATA&redirect=http://my-site.com/actual-href">Click me!</a>

Set a GET request URL as described above, with a redirect parameter included, as the href attribute of an anchor tag. When the anchor tag is clicked the event will be recorded and the API will forward the request to the URL supplied in the redirect parameter.

Read more about URL Redirects

Record multiple events

Request

# This example records 2 events to the "signups" collection and 2 events to the "purchases" collection. Notice each event created shares several common properties, including `keen.timestamp`:

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/events \
    -H 'Authorization: WRITE_KEY' \
    -H 'Content-Type: application/json' \
    -d '{
      "signups": [
        { "name" : "bob" },
        { "name" : "mary" }
      ],
      "purchases": [
        { "price": 10 },
        { "price": 20 }
      ]
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const multipleEvents = {
  signups: [
    { "username": "bob" },
    { "username": "mary" }
  ],
  purchases: [
    { "price": 10 },
    { "price": 20 }
  ],
};

client.recordEvents(multipleEvents, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});
Keen.publish_batch(
  :signups => [
    { :name => "bob" },
    { :name => "mary" }
  ],
  :purchases => [
    { :price => 10 },
    { :price => 20 }
  ]
)
keen.add_events({
  "signups": [
    { "username": "bob" },
    { "username": "mary" }
  ],
  "purchases": [
    { "price": 10 },
    { "price": 20 }
  ]
})
<?php

$signUps = [
  ['username' => 'bob']
  ['username' => 'mary']
];
$purchases = [
  ['purchase' => ['price' => 10]],
  ['purchase' => ['price' => 20]]
];

$client->addEvents(['purchases' => $purchases, 'signups' => $signUps]);

?>
  // create an event, you can create multiple events before calling upload
  - (void)viewWillAppear:(BOOL)animated
  {
        [super viewWillAppear:animated];

        NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys:@"first view", @"view_name", @"going to", @"action", nil];
        [[KeenClient sharedClient] addEvent:event toEventCollection:@"tab_views" error:nil];
  }

  // upload event
  - (void)applicationDidEnterBackground:(UIApplication *)application
{
    UIBackgroundTaskIdentifier taskId = [application beginBackgroundTaskWithExpirationHandler:^(void) {
        NSLog(@"Background task is being expired.");
    }];

    [[KeenClient sharedClient] uploadWithFinishedBlock:^(void) {
        [application endBackgroundTask:taskId];
    }];
}
    KeenClient client = KeenClient.client();
    String sampleCollection = "sampleCollection";

    Map<String, Object> sampleEvent = new HashMap<String, Object>();
    sampleEvent.put("item", "golden widget");

    for(int i = 0; i < 1000; i++) {
        client.queueEvent(sampleCollection, sampleEvent);
    }

    client.sendQueuedEvents(); //See also: sendQueuedEventsAsync()
var client = new KeenClient(new ProjectSettingsProviderEnv(), new EventCacheMemory());

// Events are added as usual, and at any time you may transmit the cached events to the server:

client.SendCachedEvents();

Response

{
  "purchases": [
    {
      "success": true
    },
    {
      "success": true
    }
  ],
  "signups": [
    {
      "success": true
    },
    {
      "success": true
    }
  ]
}

Record multiple events to one or more event collections with a single request.

Note that the API expects a JSON object whose keys are the names of each event collection you want to insert into. Each key should point to a list of events to insert for that event collection. Once the API acknowledges that your event has been stored, it may take up to 10 seconds before it will appear in query results. For bulk loading events, see the bulk loading guide.

Supported HTTP Methods

Method Authentication Description
POST Write Key Record multiple events across one or more event collections

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

A note about bulk-loading

When loading events in bulk, we recommend batches of 5,000 events or less. For historical events, be sure to overwrite the keen.timestamp property, or the API will assume each event happened at the time it was received.

See the bulk loading guide for more information.

Event Collections

Resource

https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME

The Event Collection resource provides a flexible interface for operating on a single event collection. This resource has different actions for GET, POST and DELETE requests, all of which are described in detail below.

Creating a new event collection

An event collection is created the first time an event is recorded.

Inspect a single collection

Request

$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=READ_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .get(client.url('events', 'COLLECTION_NAME'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
<?php

$client = KeenIOClient::factory([
    'projectId' => $project_id,
    'masterKey' => $master_key
]);

$results = $client->getCollection(['event_collection' => 'collection_name']);

?>
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

{
  "name": "purchases",
  "properties": {
    "item.id": "num",
    "item.on_sale": "bool",
    "item.price": "num",
    "quantity": "num",
    "screen.name": "string",
    "user.id": "num",
    "user.level": "num",
    "user.referring_source": "string"
  },
  "url": "/3.0/projects/PROJECT_ID/events/COLLECTION_NAME"
}

A GET request authenticated with a Read Key returns schema information for a single event collection, along with properties (and their types), and links to sub-resources.

Supported HTTP Methods

Method Authentication Description
GET Read Key Return schema information for a single event collection, along with properties (and their types), and links to sub-resources.

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Inspect all collections

Request

$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/events?api_key=READ_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .get(client.url('events'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
<?php

$client = KeenIOClient::factory([
    'projectId' => $project_id,
    'masterKey' => $master_key
]);

$results = $client->getCollections();

?>
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

[
  {
    "name": "purchases",
    "properties": {
      "item.id": "num",
      "item.on_sale": "bool",
      "item.price": "num",
      "quantity": "num",
      "screen.name": "string",
      "user.id": "num",
      "user.level": "num",
      "user.referring_source": "string"
    },
    "url": "/3.0/projects/PROJECT_ID/events/purchases"
  },
  {
    "name": "level_ups",
    "properties": {
      "from_level": "num",
      "level": "num",
      "screen.name": "string",
      "to_level": "num",
      "user.id": "num",
      "user.level": "num",
      "user.prior_balance": "num",
      "user.referring_source": "string"
    },
    "url": "/3.0/projects/PROJECT_ID/events/level_ups"
  },
  {
    "name": "logins",
    "properties": {
      "user.email": "string",
      "user.id": "string",
      "user_agent.browser": "string",
      "user_agent.browser_version": "string",
      "user_agent.platform": "string"
    },
    "url": "/3.0/projects/PROJECT_ID/events/logins"
  }
]

Return schema information for all the event collections in a given project, along with properties (and their types), and links to sub-resources.

Supported HTTP Methods

Method Authentication Description
GET Read Key Return schema information for all the event collections in a given project, along with properties (and their types), and links to sub-resources.

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Properties

The Property resource is a simple interface for inspecting or deleting specified properties for a given event collection.

Properties are pieces of information that describe an event and relevant information about things related to that event.

While we generally believe that it can’t hurt to have too much information, we have put some practical limits in place: there cannot be more than 1,000 properties per event collection. Exceeding this number is usually caused by the dynamic naming of properties.

Inferred Data types

When an event is recorded, each property’s data type is automatically inferred to be one of the following:

Type Description
string string of characters
number number or decimal
boolean either true or false
array a list of data points, of the same type

Property Name Rules

  • Must be less than 256 characters long
  • There cannot be any periods (.) in the name
  • They cannot be a null value

Property Value Rules

  • String values must be less than 10,000 characters long
  • Numeric values must be between -2^63 (-9223372036854775808) and 2^63 - 1 (9223372036854775807) (inclusive)
  • Values in lists must themselves follow the above rules
  • Values in dictionaries must themselves follow the above rules

Property hierarchies

Example data model

{
  "customer": {
    "id": "0808130sdfsf801",
    "email": "email@address.com"
  },
  "store": {
    "name": "Whole Foods Market",
    "address": {
      "street": "2001 Market St",
      "city": "San Francisco",
      "state": "California"
    }
  }
}

We highly recommend organizing your event properties into hierarchies to alleviate confusion and de-clutter in your data model.

Properties in this example data model can be accessed in a clean and intuitive way: customer.id and store.address.city.

Inspect a single property

Request

$ curl "https://api.keen.io/v3/projects/PROJECT_ID/events/COLLECTION_NAME/properties/PROPERTY_NAME?api_key=READ_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .get(client.url('events', 'COLLECTION_NAME', 'properties', 'keen.id'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
<?php

$client = KeenIOClient::factory([
    'projectId' => $project_id,
    'masterKey' => $master_key
]);

$results = $client->getProperty(['event_collection' => 'example_collection_name', 'property_name' => 'example_property_name']);

?>

Response

{
  "property_name": "PROPERTY_NAME",
  "url": "/3.0/projects/PROJECT_ID/events/COLLECTION_NAME/properties/PROPERTY_NAME",
  "type": "string"
}

Return details for a single property in a given event collection.

Supported HTTP Methods

Method Authentication Description
GET Read Key Return details for a single property of an event collection.

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Timestamps

The API uses the ISO-8601 format; an international standard for representing time data. All time data is stored in UTC as well.

The ISO-8601 format is as follows:

{YYYY}-{MM}-{DD}T{hh}:{mm}:{ss}.{SSS}{TZ}

Pattern Description
YYYY Four digit year. Example: “2014”
MM Two digit month. Example: January would be “01”
DD Two digit day. Example: The first of the month would be “01”
hh Two digit hour. Example: The hours for 12:01am would be “00” and the hours for 11:15pm would be “23”
mm Two digit minute
ss Two digit second
SSS Milliseconds to the third decimal place
TZ Time zone offset. Specify a positive or negative integer. To specify UTC, add “Z” to the end. Example: To specify Pacific time (UTC-8 hours), you should append “-0800” to the end of your date string

Example ISO-8601 date strings:

  • 2012-01-01T00:01:00-08:00
  • 1996-02-29T15:30:00+12:00
  • 2000-05-30T12:12:12Z

Dynamic placeholders

Example Usage

$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "ip_address": "${keen.ip}",
      "user_agent": "${keen.user_agent}"
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  ip_address: '${keen.ip}',
  user_agent: '${keen.user_agent}'
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "ip_address": "192.168.0.1",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36"
}

Some properties pertain to information about the client sending data. To make this collection simple and configurable, we’ve introduced template-like strings that, when used as event properties, will change dynamically based on the client sending data.

Placeholder Description
${keen.ip} Replaced with the IP address of the client.
${keen.user_agent} Replaced with the user agent string of the client.

The “keen” object

Example

{
  "keen": {
    "created_at": "2012-12-14T20:24:01.123000+00:00",
    "timestamp": "2012-12-14T20:24:01.123000+00:00",
    "id": "asd9fadifjaqw9asdfasdf939"
  },
  "device": {
    "id": "0980980231sdfsss",
    "model": "H09 Beta"
  }
}

A special keen object is automatically attached to every event when recorded. It contains three standard properties and two optional properties.

Standard properties

Property Overwrite? Description
id No A unique ID, used internally to ensure once-only writes.
created_at No An ISO-8601 timestamp, set at the time each event is recorded.
timestamp Yes An ISO-8601 timestamp, set to the same time as keen.created_at unless a value is already present when received (useful when bulk-loading historical data).

Optional Properties

Example

{
  "keen": {
    "location": {
      "coordinates": [ -88.21337, 40.11041 ]
    },
    "addons": [ {} ]
  },
  "user": {
    "name": "Smacko",
    "age": 21
  }
}
Property Description
keen.location Enable geo filtering by including a pair of coordinates within keen.location of each event. These coordinates should be specified as an array of the longitude and latitude values in decimal format (up to 6 decimal places). Please note that the coordinates are in the format longitude followed by latitude. This is a GeoJSON Standard.
keen.addons Enable various data enrichment add-ons by passing an array of configuration objects. This is described in detail below.

Data Enrichment

Keen IO can enrich event data by parsing or joining it with other data sets. This is done through the concept of “add-ons”.

Example

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "keen": {
          "addons": [
            {
              "name": "addon:name",
              "input": {
                "parameter_name" : "where.to.find.parameter"
              },
              "output": "where.to.put.output"
            }
          ]
        },
        "user": {
          "name": "Smacko",
          "age": 21
        }
    }'
{
  "keen": {
    "addons": [
      {
        "name": "addon:name",
        "input": {
          "parameter_name" : "where.to.find.parameter"
        },
        "output": "where.to.put.output"
      }
    ]
  },
  "user": {
    "name": "Smacko",
    "age": 21
  }
}

Configuration

Add-ons can be activated by passing an array of configuration objects to the keen.addons property of each event.

Each configuration object contains the following parameters:

Property Description
name A string indicating the name of the add-on.
input An object containing required parameters based on the add-on.
output A property in which to store the enriched data. This property can be nested, and does not necessarily need to exist prior, but cannot be placed in the keen namespace.

IP to Geo parser

Example Usage

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
  "ip_address": "${keen.ip}",
  "keen": {
    "addons": [
      {
        "name" : "keen:ip_to_geo",
        "input" : {
          "ip" : "ip_address"
        },
        "output" : "ip_geo_info"
      }
    ]
  }
}'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  ip_address: '${keen.ip}',
  keen: {
    addons: [
      {
        name: 'keen:ip_to_geo',
        input: {
          ip: 'ip_address'
        },
        output: 'ip_geo_info'
      }
    ]
  }
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "ip_address": "192.168.0.1",
  "ip_geo_info": {
    "city" : "San Francisco",
    "province" : "California",
    "country" : "United States",
    "country_code": "US",
    "continent" : "North America",
    "postal_code" : "94122",
    "coordinates" : [-122.42005, 37.77479]
  },
  "keen": {
    "created_at": "2012-12-14T20:24:01.123000+00:00",
    "timestamp": "2012-12-14T20:24:01.123000+00:00",
    "id": "asd9fadifjaqw9asdfasdf939"
  }
}

This add-on uses a client’s IP address to add data about the geographical location of the client when the event was recorded.

Activate this add-on

The parameters for the IP to Geo add-on are as follows:

Parameter Description
name “keen:ip_to_geo”
input An object with a key of “ip” and a value of the name of the property containing the IP address to parse.
{ "ip": "property.containing.ip_address" }
output A property name describing where the produced object should be stored.

Output properties

Property Description
city City associated with the client’s IP address.
province State/province associated with the client’s IP address.
country Country associated with the client’s IP address.
country_code ISO country code associated with the client’s IP address.
continent Continent associated with the client’s IP address.
postal_code Postal code associated with the client’s IP address.
coordinates List of geo coordinates, longitude followed by latitude.

User Agent parser

Example Usage

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "user_agent": "${keen.user_agent}",
      "keen": {
        "addons": [
          {
            "name" : "keen:ua_parser",
            "input" : {
              "ua_string" : "user_agent"
            },
            "output" : "parsed_user_agent"
          }
        ]
      }
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  user_agent: '${keen.user_agent}',
  keen: {
    addons: [
      {
        name: 'keen:ua_parser',
        input: {
          ua_string: 'user_agent'
        },
        output: 'parsed_user_agent'
      }
    ]
  }
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "user_agent": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
  "parsed_user_agent" : {
    "device" : {
      "family" : "iPhone"
    },
    "browser" : {
      "family" : "Chrome Mobile iOS",
      "major" : 19,
      "minor" : 0,
      "patch" : 1084
    },
    "os" : {
      "family" : "iOS",
      "major" : 5 ,
      "minor" : 1,
      "patch" : 1,
      "patch_minor" : null
    }
  },
  "keen": {
    "created_at": "2012-12-14T20:24:01.123000+00:00",
    "timestamp": "2012-12-14T20:24:01.123000+00:00",
    "id": "asd9fadifjaqw9asdfasdf939"
  }
}

This add-on will take a user agent string and parse it into the device, browser, browser version, operating system, and operating system version.

Activate this add-on

Parameter Description
name “keen:ua_parser”
input An object with a key of “ua_string” and a value of the name of the property containing the user agent string to parse.
{ "ua_string": "property.containing.ua_string" }
output A property name describing where the produced object should be stored.

Output properties

Property Description
device An object containing info about the device.
device.family A string containing the device family. ie: “iPhone”
browser An object containing info about the browser.
browser.family A string containing the family of the browser. ie: “Firefox”
browser.major A number indicating the major version of the browser.
browser.minor A number indicating the minor version of the browser.
browser.patch A number indicating the patch of the browser.
os An object containing info about the os.
os.family A string containing the family of the operating system. ie: “Windows 10”
os.major A number indicating the major version of the operating system.
os.minor A number indicating the minor version of the operating system.
os.patch A number indicating the patch of the operating system.
os.patch_minor A number indicating the minor version of the patch of the operating system.

URL parser

Example Usage

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "page_url" : "http://my-website.com/cool/link?source=twitter&foo=bar/#title",
      "keen" : {
        "addons" : [
          {
            "name" : "keen:url_parser",
            "input" : {
              "url" : "page_url"
            },
            "output" : "parsed_page_url"
          }
        ]
      }
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  page_url: document.location.href,
  keen: {
    addons: [
      {
        name: 'keen:url_parser',
        input: {
          url: 'page_url'
        },
        output: 'parsed_page_url'
      }
    ]
  }
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "page_url": "http://my-website.com/cool/link?source=twitter&foo=bar/#title",
  "parsed_page_url": {
    "protocol" : "http",
    "domain" : "my-website.com",
    "path" : "/cool/link",
    "anchor" : "title",
    "query_string" : {
      "source" : "twitter",
      "foo" : "bar"
    }
  },
  "keen": {
    "created_at": "2012-12-14T20:24:01.123000+00:00",
    "timestamp": "2012-12-14T20:24:01.123000+00:00",
    "id": "asd9fadifjaqw9asdfasdf939"
  }
}

This add-on will take a well-formed URL and parse it into its component pieces for rich multi-dimensional analysis.

Activate this add-on

Parameter Description
name “keen:url_parser”
input An object with a key of “url” and a value of the name of the property containing the URL to parse.
{ "url": "property.containing.url" }
output A property name describing where the produced object should be stored.

Output properties

Property Description
protocol A string containing the protocol of the URL (http or https).
domain A string containing the domain of the URL. ie: “keen.io”
path A string containing the path of the URL. ie: “/super-awesome-content”
anchor A string containing the anchor tag of the URL. ie: “title”
query_string An object containing key:value pairs of each parameter in the query string. ie: “source” : “twitter”

Referrer parser

Example Usage

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "referrer" : {
        "url": "https://search-engine.com?search=analytics"
      },
      "page": {
        "url": "http://mysite.com/landing-page"
      },
      "keen" : {
        "addons" : [
          {
            "name" : "keen:referrer_parser",
            "input" : {
              "referrer_url" : "referrer.url",
              "page_url" : "page.url"
            },
            "output" : "referrer.info"
          }
        ]
      }
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  page: {
    url: document.location.href
  },
  referrer: {
    info: { /* Enriched */ },
    url: document.referrer
  },
  keen: {
    addons: [
      {
        name: 'keen:referrer_parser',
        input: {
          page_url: 'page.url',
          referrer_url: 'referrer.url'
        },
        output: 'referrer.info'
      }
    ]
  }
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "referrer": {
    "url": "https://search-engine.com?search=analytics",
    "info": {
      "medium" : "SEARCH",
      "source" : "search-engine.com",
      "term" : "analytics"
    }
  },
  "page": {
    "url": "http://mysite.com/landing-page"
  },
  "keen": {
    "created_at": "2012-12-14T20:24:01.123000+00:00",
    "timestamp": "2012-12-14T20:24:01.123000+00:00",
    "id": "asd9fadifjaqw9asdfasdf939"
  }
}

This add-on will take a well-formed referrer URL and parse it into its source.

Activate this add-on

Parameter Description
name “keen:referrer_parser”
input An object with two properties:
A key of “referrer_url” with a value of the name of the property containing the referrer URL to parse.
A key of “page_url” with a value of the name of the property containing the URL of the current page.
output A property name describing where the produced object should be stored.

Output properties

Property Description
medium A string containing the general category of the source of the referrer. See the chart below for potential mediums. ie: “SEARCH”
source A string containing the origin or source of the referrer. ie: “Google”
term A string containing the search term of the referrer, if it contains one.

Potential mediums

Medium Description
UNKNOWN If the parser can’t figure out the medium.
INTERNAL If the domain of the referring URL matches the domain of the page_url.
SEARCH If search was the referrer.
EMAIL If an email client was the referrer.

Datetime parser

Example Usage

# Taken from Sending A Single Event.  The only change is the content of the json
$ curl http://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: WRITE_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
  "keen" : {
    "timestamp": "2016-05-21T16:36:40.092Z",
    "addons": [
      {
        "name": "keen:date_time_parser",
        "input": {
          "date_time": "keen.timestamp"                                
        },
        "output": "timestamp_info"
      }
    ]
  }
}'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  writeKey: 'WRITE_KEY'
});

const eventBody = {
  keen: {
    timestamp: new Date().toISOString(),
    addons: [
      {
        name: 'keen:date_time_parser',
        input: {
          date_time: 'keen.timestamp'
        },
        output: 'timestamp_info'
      }
    ]
  }
};

client.recordEvent('pageviews', eventBody, (err, res) => {
  if (err) {
    // Handle error
  }
  else {
    // Handle response
  }
});

Result

{
  "keen": {
    "timestamp": "2016-05-21T16:36:40.092Z"
  },
  "timestamp_info": {
    "millisecond": 92,
    "day_of_week_string": "Saturday",
    "hour": 16,
    "timezone_offset": 0,
    "day_of_month": 21,
    "day_of_week": 6,
    "month": 5,
    "second": 40,
    "week": 20,
    "year": 2016,
    "minute": 36
  }
}

This add-on uses a specified keen.timestamp property, but you can reference the keen.timestamp property with providing it yourself (it will use system time when we receive the event).

Activate this add-on

The parameters for the Datetime parser add-on are as follows:

Parameter Description
name “keen:date_time_parser”
input An object with a key of “date_time” and a value of the name of the property containing a datetime to parse.
{ "date_time": "property.containing.datetime" }
output A property name describing where the produced object should be stored.

Output properties

Property Description
millisecond The millisecond of the datetime provided.
day_of_week_string The day of week in string form. ie: “Saturday”
hour The universal time (00:00 - 24:00) hour of the datetime provided.
timezone_offset The numerical timezone offset from UTC of the datetime provided. ie: 0
day_of_month The day of month of the datetime provided.
day_of_week The day of week in numerical form: ie: 5
month The month of the datetime provided
second The second of the datetime provided
week The week of the datetime provided
year The year of the datetime provided
minute The minute of the datetime provided

Compute

Analyses

You can perform a wide range of analyses in Keen by running queries on your event data. The different types of analysis and the associated queries are described in detail in this section.

Note: Numeric Aggregations on Non-Numbers

The API supports aggregations that only apply to numeric values. Minimum, maximum, average, and sum are really only useful when applied in the context of numbers.

For this reason, we automatically filter out events that have non-numeric data when these kinds of aggregations are requested.

As an example, if you have two events in your “purchases” collection, where the “item.price” property has values 24.50 and “hello”, an Average analysis on this collection and “item.price” will return 24.50, not 12.25. The second event is completely ignored.

For this reason, amongst others, we highly recommend only using data of a single type for any particular property.

Count

Request

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/count?api_key=READ_KEY&event_collection=COLLECTION_NAME"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'purchases',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("clicks", :timeframe => "this_7_days") # => 100
keen.count("clicks", timeframe="this_7_days") # => 100
<?php

$totalPurchases = $client->count("clicks", ["timeframe" => "this_7_days"]); // => 100

?>
KIOQuery *countQuery = [[KIOQuery alloc] initWithQuery:@"count" andPropertiesDictionary:@{@"event_collection": @"collection", @"timeframe": @"this_14_days"}];

[[KeenClient sharedClient] runAsyncQuery:countQuery block:countQueryCompleted];
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
long count = queryClient.count("<event_collection>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var count = keenClient.Query(QueryType.Count(), "target_collection", relativeTimeframe, timezone: timezone);

Response

{
  "result": 100
}

Return the number of events in the collection matching given criteria.

This type of analysis can help answer questions such as:

  • How many purchases have been made by users from Iowa in the previous two weeks?
  • How many times has a landing page been viewed?

HTTP Methods

Method Authentication Description
GET Read Key Returns the number of events in the event collection matching the given criteria. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the number of events in the event collection matching the given criteria. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Count Unique

Request

export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/count_unique?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count_unique \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count_unique', {
    event_collection: 'purchases',
    target_property: 'username',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count_unique("purchases", :target_property => "username", :timeframe => "this_7_days")  # => 78
keen.count_unique("purchases", target_property="username", timeframe="this_7_days") # => 78
<?php

$totalItems = $client->countUnique("purchases", ["target_property" => "username", "timeframe" => "this_7_days"] ); // => 78

?>
KIOQuery *countUniqueQuery = [[KIOQuery alloc] initWithQuery:@"count_unique" andPropertiesDictionary:@{@"event_collection": @"collection", @"target_property": @"key", @"timeframe": @"this_14_days"}];

[[KeenClient sharedClient] runAsyncQuery:countUniqueQuery block:countQueryCompleted];
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
long countUnique = queryClient.countUnique("<event_collection>", "<target_property>", new AbsoluteTimeframe("2015-05-15T19:00:00.000Z","2015-06-07T19:00:00.000Z"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var countUnique = keenClient.Query(QueryType.CountUnique(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 78
}

Return the number of events with unique values, for a target property in a collection matching given criteria. A common use for this is to count the number of unique users who performed an event.

This type of analysis can help answer questions such as:

  • How many unique users have logged in to my application?
  • How many unique people have viewed a landing page in the previous week?
  • How many different companies are using our app?
  • In how many different countries is our app being used?

HTTP Methods

Method Authentication Description
GET Read Key Returns the number of events in the collection containing unique values for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the number of events in the collection containing unique values for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.
force_exact A boolean value that instructs the query to fail if the result is so big it requires us to approximate the answer.

Approximation

The API will switch from an exact count to an approximated count above 1,000,000 distinct values. The optional force_exact parameter will cause the query to fail, rather than return an approximated count. If you want to geek out about statistics, give us a shout.

Limits

Queries are rate limited at 200/minute.

Minimum

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/minimum?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/minimum \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
       \"timeframe\": \"this_7_days\"
      }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('minimum', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.minimum("purchases", :target_property => "price", :timeframe => "this_7_days")  # => 20.78
keen.minimum("purchases", target_property="price", timeframe="this_7_days") # => 20.78
<?php

$minimum = $client->minimum("purchases", ["target_property" => "price", "timeframe" => "this_7_days"]); // => 20.78

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double minimum = queryClient.minimum("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var minimum = keenClient.Query(QueryType.Minimum(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 20.78
}

Return the minimum numeric value for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

HTTP Methods

Method Authentication Description
GET Read Key Returns the minimum numeric value for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the minimum numeric value for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Maximum

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/maximum?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/maximum \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('maximum', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.maximum("purchases", :target_property => "price", :timeframe => "this_7_days")  # => 1575.52
keen.maximum("purchases", target_property="price", timeframe="this_7_days") # => 1575.52
<?php

$maximum = $client->maximum("purchases", ["target_property" => "price", "timeframe" => "this_7_days"]); // => 1575.52

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double maximum = queryClient.maximum("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var Maximum = keenClient.Query(QueryType.Maximum(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 1575.52
}

Return the maximum numeric value for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

HTTP Methods

Method Authentication Description
GET Read Key Returns the maximum numeric value for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the maximum numeric value for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Sum

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/sum?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/sum \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('sum', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.sum("purchases", :target_property => "price", :timeframe => "this_7_days")  # => 189423.67
keen.sum("purchases", target_property="price", timeframe="this_7_days") # => 189423.67
<?php

$sum = $client->sum("purchases", ["target_property" => "price", "timeframe" => "this_7_days"]); => 189423.67

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double sum = queryClient.sum("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var sum = keenClient.Query(QueryType.sum(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 189423.67
}

Calculate the sum of all numeric values for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

HTTP Methods

Method Authentication Description
GET Read Key Returns the sum of all numeric values for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the sum of all numeric values for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Average

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/average?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/average \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('average', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.average("purchases", :target_property => "price", :timeframe => "this_7_days") # => 58.20
keen.average("purchases", target_property="price", timeframe="this_7_days") # => 58.20
<?php

$average = $client->average("purchases", ["target_property" => "price", "timeframe" => "this_7_days"]); // => 58.20

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double average = queryClient.average("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var average = keenClient.Query(QueryType.average(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 58.20
}

Calculate the average value for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

HTTP Methods

Method Authentication Description
GET Read Key Returns the average value for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the average value for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Median

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/median?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/median \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('median', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.median("purchases", :target_property => "price", :timeframe => "this_7_days") # => 64.1
keen.median("purchases", target_property="price", timeframe="this_7_days") # => 58.20
<?php

$median = $client->median("purchases", ["target_property" => "price", "timeframe" => "this_7_days"]); // => 64.1

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double median = queryClient.median("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var median = keenClient.Query(QueryType.Median(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 64.1
}

Calculate the median value for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

Median can have up to approximately 3% error on high cardinality data sets with extreme domains. If you need higher accuracy and are willing to sacrifice performance, let us know!

HTTP Methods

Method Authentication Description
GET Read Key Returns the median value for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the median value for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Approximation

The API will switch from an exact result to an approximated result above 1,000,000 distinct values. The optional force_exact parameter will cause the query to fail, rather than return an approximated result. If you want to geek out about statistics, give us a shout.

Limits

Queries are rate limited at 200/minute.

Percentile

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY
$ export PERCENTILE=30

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/percentile?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY&percentile=PERCENTILE"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/percentile \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"percentile\": PERCENTILE,
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('percentile', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days',
    percentile: 90
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.percentile("purchases", :target_property => "price", :percentile => 90, :timeframe => "this_7_days") # => 2
// Currently not supported by this SDK.
<?php

$percentile = $client->percentile("purchases", ["target_property" => "price", "percentile" => 90, "timeframe" => "this_7_days"]); // => 2

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
double percentile = queryClient.percentile("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var percentile = keenClient.Query(QueryType.percentile(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": 2
}

Calculate a specified percentile value for a target property, among all events in a collection matching given criteria.

Non-numeric values will be ignored. If none of the property values are numeric, the API returns an error.

Percentile can have up to approximately 3% error on high cardinality data sets with extreme domains. If you need higher accuracy and are willing to sacrifice performance, let us know!

HTTP Methods

Method Authentication Description
GET Read Key Returns a specified percentile value for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns a requested percentile value for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
percentile Specifies the percentile to calculate, supporting 0-100 with two decimal places of precision. Example: 99.99
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Approximation

The API will switch from an exact result to an approximated result above 1,000,000 distinct values. The optional force_exact parameter will cause the query to fail, rather than return an approximated result. If you want to geek out about statistics, give us a shout.

Limits

Queries are rate limited at 200/minute.

Select Unique

Request

$ export TARGET_PROPERTY=TARGET_PROPERTY

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/select_unique?api_key=READ_KEY&event_collection=COLLECTION_NAME&target_property=TARGET_PROPERTY"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/select_unique \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"target_property\": \"TARGET_PROPERTY\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('select_unique', {
    event_collection: 'purchases',
    target_property: 'user.email',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.select_unique("purchases", :target_property => "user.email", :timeframe => "this_7_days")  # => ["bob@aol.com", "joe@yahoo.biz", "travis@gmail.com"]
keen.select_unique("purchases", target_property="user.email", timeframe="this_7_days" ) # => ["bob@aol.com", "joe@yahoo.biz", "travis@gmail.com"]
<?php

$items = $client->selectUnique("purchases", ["target_property" => "user.email", :"timeframe" => "this_7_days"]); // => ["bob@aol.com", "joe@yahoo.biz", "travis@gmail.com"]

?>
// Supported
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.SELECT_UNIQUE)
        .withEventCollection("<event_collection>")
        .withTargetProperty("click-number")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);
if (result.isListResult()) {
    List<QueryResult> listResults = result.getListResults();
    for (QueryResult item : listResults) {
        if (item.isLong()) {
            // do something with long value
        }
    }
}
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var selectUnique = keenClient.Query(QueryType.SelectUnique(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": [
    "bob@aol.com",
    "joe@yahoo.biz",
    "travis@gmail.com"
  ]
}

Return a list of unique property values for a target property, among all events in a collection matching given criteria.

Some example uses for this analysis type:

  • List all of the email addresses for people who used a certain feature
  • List all of the countries where your app is being used
  • List all of the devices or browsers on which your app is being used
  • List all of the users who have purchased an upgrade for your app

HTTP Methods

Method Authentication Description
GET Read Key Returns a list of unique property values for a target property. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns a list of unique property values for a target property. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
target_property Specifies the name of the property to analyze.
timeframe Limits analysis to a specific period of time when the events occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Absent Values

A “missing” value is still considered a unique value. Targeting a property that has not been defined will return a single result.

This makes it possible to differentiate between events that have a property defined and those that do not. To avoid this behavior, just include an “exists” filter for that property.

Limits

Queries are rate limited at 200/minute.

Multi-Analysis

Request

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/multi_analysis?api_key=READ_KEY&event_collection=COLLECTION_NAME&analyses=ENCODED_ANALYSIS_OBJECT"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/multi_analysis \
    -H 'Authorization: READ_KEY' \
    -H 'Content-Type: application/json' \
    -d '{
      "event_collection": "COLLECTION_NAME",
      "analyses": {
        "unique users": {
          "analysis_type": "count_unique",
          "target_property": "user.id"
        },
        "total visits": {
          "analysis_type": "count"
        }
      },
      "timeframe": "this_7_days"
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('multi_analysis', {
    event_collection: 'pageviews',
    analyses: {
      'unique users': {
        analysis_type: 'count_unique',
        target_property: 'user.id'
      }
      'total visits': {
        analysis_type: 'count'
      }
    },
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.multi_analysis("purchases", analyses: {
  :avg_price => {
    :analysis_type => "average",
    :target_property => "price"
  },
  :max_price => {
    :analysis_type => "maximum",
    :target_property => "price"
  }
},
:timeframe => "this_7_days") # => {:"avg_price" => 52.79, :"max_price" => 736.41}
keen.multi_analysis("purchases",
  analyses={
    "avg_price": {
      "analysis_type": "average",
      "target_property": "price"
    },
    "max_price":{
      "analysis_type": "maximum",
      "target_property": "price"
    }
  },
  "timeframe": "this_7_days") # => {"avg_price": 52.79, "max_price": 736.41}
<?php

$analyses = [
    "avg_price" => [
      "analysis_type" => "average",
      "target_property" => "price"
    ],
    "max_price" => [
      "analysis_type" => "maximum",
      "target_property" => "price"
    ]
];
$stats = $client->multiAnalysis("purchases", ["analyses" => $analyses], ["timeframe" => "this_7_days"]);
// => ["avg_price" => 52.79, "max_price" => 736.41]

?>
KIOQuery *countQuery = [[KIOQuery alloc] initWithQuery:@"count" andPropertiesDictionary:@{@"event_collection": @"collection", @"timeframe": @"this_14_days"}];
KIOQuery *countUniqueQuery = [[KIOQuery alloc] initWithQuery:@"count_unique" andPropertiesDictionary:@{@"event_collection": @"collection", @"target_property": @"key", @"timeframe": @"this_14_days"}];

// Optionally set a name for your queries, so it's easier to check the results
[countQuery setQueryName:@"count_query"];
[countUniqueQuery setQueryName:@"count_unique_query"];

[[KeenClient sharedClient] runAsyncMultiAnalysisWithQueries:@[countQuery, countUniqueQuery] block:countQueryCompleted];
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
final MultiAnalysis multiAnalysis = new MultiAnalysis.Builder()
        .withEventCollection("the_collection")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .withSubAnalysis(new SubAnalysis("label_for_count", QueryType.COUNT))
        .withSubAnalysis(new SubAnalysis("sum_analysis_label", QueryType.SUM, "property_to_sum"))
        .build();

QueryResult result = this.queryClient.execute(multiAnalysis);

if (result instanceof MultiAnalysisResult) {
    MultiAnalysisResult multiAnalysisResult = (MultiAnalysisResult)result;

    for (String subAnalysisLabel : multiAnalysisResult.getAllResults().keySet()) {
        QueryResult resultForSubAnalysis = multiAnalysisResult.getResultFor(subAnalysisLabel);
        // ... do something with the results of the various sub-analyses.
    }
}
IEnumerable<MultiAnalysisParam> analyses = new List<MultiAnalysisParam>()
{
    new MultiAnalysisParam("purchases", MultiAnalysisParam.Metric.Count()),
    new MultiAnalysisParam("max_price", MultiAnalysisParam.Metric.Maximum("price")),
    new MultiAnalysisParam("min_price", MultiAnalysisParam.Metric.Minimum("price"))
};

var result = keenClient.QueryMultiAnalysis("purchases", analyses);

var purchases = int.Parse(result["purchases"]);
var maxPrice = float.Parse(result["max_price"]);

Response

{
  "result": {
    "unique users" : 5291,
    "total visits" : 21392
  }
}

Multi-analysis lets you run multiple types of analyses over the same data. For example, you could count the number of purchases you’ve had in the last week, as well as sum the price of the goods sold, all in one API call!

Multi-analysis currently supports:

HTTP Methods

Method Authentication Description
GET Read Key Returns the results for multiple analyses.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
analyses A URL-encoded JSON object that defines the multiple types of analyses to perform.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timeframe Refines the scope of events to be included in the analysis based on when the event occurred.
timezone Assigns a timezone offset to relative timeframes.
group_by Specifies the name of a property by which to group results. Using this parameter changes the response format.
interval Specifies the size of time interval by which to group results. Using this parameter changes the response format.

Limits

Queries are rate limited at 200/minute.

Funnels

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/funnel \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "steps": [
        {
         "event_collection": "signed up",
         "actor_property": "visitor.guid",
         "timeframe": "this_7_days"
        },
        {
         "event_collection": "completed profile",
         "actor_property": "user.guid",
         "timeframe": "this_7_days"
        },
        {
         "event_collection": "referred user",
         "actor_property": "user.guid",
         "timeframe": "this_7_days"
        }
      ]
   }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('funnel', {
    steps: [
      {
        event_collection: 'signed up',
        actor_property: 'visitor.guid',
        timeframe: 'this_7_days'
      },
      {
        event_collection: 'completed profile',
        actor_property: 'user.guid',
        timeframe: 'this_7_days'
      },
      {
        event_collection: 'referred user',
        actor_property: 'user.guid',
        timeframe: 'this_7_days'
      }
    ]
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.funnel(:steps => [
  {
    :event_collection => "signed up",
    :actor_property => "visitor.guid",
    :timeframe => "this_7_days"
  },
  {
    :event_collection => "completed profile",
    :actor_property => "user.guid",
    :timeframe => "this_7_days"
  },
  {
    :event_collection => "referred user",
    :actor_property => "user.guid",
    :timeframe => "this_7_days"
  }
])
step1 = {
  "event_collection": "signed up",
  "actor_property": "visitor.guid",
  "timeframe": "this_7_days"
}
step2 = {
  "event_collection": "completed profile",
  "actor_property": "user.guid",
  "timeframe": "this_7_days"
}
step3 = {
  "event_collection": "referred user",
  "actor_property": "user.guid",
  "timeframe": "this_7_days"
}
keen.funnel([step1, step2, step3])
<?php

$step1 = [
  "event_collection" => "signed up",
  "actor_property" => "visitor.guid",
  "timeframe" => "this_7_days"
];
$step2 = [
   "event_collection" => "completed profile",
   "actor_property" => "user.guid",
   "timeframe" => "this_7_days"
];
$step3 = [
   "event_collection" => "referred user",
   "actor_property" => "user.guid",
   "timeframe" => "this_7_days"
];
$client->funnel([$step1, $step2, $step3]);

?>
IEnumerable<FunnelStep> funnelSteps = new List<FunnelStep>
{
    new FunnelStep
    {
        EventCollection = "registered_users",
        ActorProperty = "id"
    },
    new FunnelStep
    {
        EventCollection = "subscribed_users",
        ActorProperty = "user_id"
    },
};

var result = keenClient.QueryFunnel(funnelSteps);

var registeredUsers = result.ElementAt(0);
var registeredAndSubscribedUserCount = result.ElementAt(1);
KIOQuery *funnelQuery = [[KIOQuery alloc] initWithQuery:@"funnel" andPropertiesDictionary:@{@"timeframe": @"this_14_days", @"steps": @[@{@"event_collection": @"user_signed_up",
            @"actor_property": @"user.id"},
          @{@"event_collection": @"user_completed_profile",
            @"actor_property": @"user.id"}]}];

[[KeenClient sharedClient] runAsyncQuery:funnelQuery block:countQueryCompleted];
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// funnel 
final Funnel funnel = new Funnel.Builder()
        .withStep(new FunnelStep("signed_up", "visitor.guid", new RelativeTimeframe("this_7_days")))
        .withStep(new FunnelStep("completed_profile", "user.guid", new RelativeTimeframe("this_7_days")))
        .withStep(new FunnelStep("referred_user", "user.guid", new RelativeTimeframe("this_7_days", "UTC")))
        .build();

QueryResult result = this.queryClient.execute(funnel);

if (result instanceof FunnelResult) {
    // The result was a FunnelResult as expected.
    // Cast the result to the appropriate type.
    FunnelResult funnelResult = (FunnelResult)result;
    // Get the sub-result for the funnel analysis
    ListResult funnelListResult = funnelResult.getFunnelResult();
    // Unpack the list of QueryResults for each funnel step
    List<QueryResult> funnelResultData = funnelListResult.getListResults();
    // Iterate through each funnel step result
    for (QueryResult stepResult : funnelResultData) {
        if (stepResult instanceof LongResult) {
            // Do something with each result of the funnel.
            long stepData = stepResult.longValue();
        }
    }

    // Get the actors result, which may be null.
    // In the case of this example, no steps requested actor values
    // so it indeed would be null, but FunnelStep has an optional parameter
    // to request actor values which will populate this result.
    ListResult actorsResult = funnelResult.getActorsResult();
    if (null != result.getActorsResult()) {
        // A list of actor values was provided in the response.
        // Unpack the list of lists of actors
        List<QueryResult> actorsResultLists = actorsResult.getListResults();
        for (QueryResult stepActorsResult : actorsResultLists) {
            // Get the list of actors for this step
            List<QueryResult> stepActorsResultList = stepActorsResult.getListResults();
            // Iterate through all actor values
            for (QueryResult actorResult : stepActorsResultList) {
                // Unpack the actor value
                if (actorResult instanceof StringResult) {
                    String actorValue = actorResult.stringValue();
                }
                else if (actorResult instanceof LongResult) {
                    long actorValue = actorResult.longValue();
                }
            }
        }
    }
}

Response

{
  "result": [
    3,
    1,
    0
  ],
  "steps": [
    {
      "actor_property": "visitor.guid",
      "event_collection": "signed up",
      "timeframe": "this_7_days"
    },
    {
      "actor_property": "user.guid",
      "event_collection": "completed profile",
      "timeframe": "this_7_days"
    },
    {
      "actor_property": "user.guid",
      "event_collection": "referred user",
      "timeframe": "this_7_days"
    }
  ]
}

Returns the number of unique actors that successfully (or unsuccessfully) make it through a series of steps. “Actors” could mean users, devices, or any other identifiers that are meaningful to you.

Actors will fall off at each step, to varying degrees, so a funnel analysis reveals where a given flow loses the most users. This helps identify areas for improvement, as well as the overall health of your business.

For example, a funnel could include these steps:

  1. Successful completion of an app’s tutorial
  2. Creation of content in the app
  3. Sharing of content with another user

A funnel analysis with those steps would work like this:

  1. Count the number of unique users who completed the app’s tutorial.
  2. Of the users who were counted in step 1, count the number of them who created content.
  3. Of the users who were counted in step 2, count the number of them who shared content.

HTTP Methods

Method Authentication Description
GET Read Key Returns the number of unique actors that successfully (or unsuccessfully) make it through a series of provided steps. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Returns the number of unique actors that successfully (or unsuccessfully) make it through a series of provided steps. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
steps A URL encoded JSON Array defining the steps in the funnel.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
timeframe Refines the scope of events to be included in the analysis based on when the event occurred.
timezone Assigns a timezone offset to relative timeframes.

Limits

Queries are rate limited at 200/minute.

Funnels are limited to 2 million unique actors per request.

Steps

A step is defined as an event or set of events that meet given criteria. The first step, along with any filters that you provide, determine the starting data set of the funnel.

Each step includes an actor_property (typically a user ID) that specifies the important thing you want to count in that step. Continuing our example, the first step would count the number of unique user IDs in the event collection “Tutorial Completions”.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
actor_property Specifies the name of the property to use as a unique identifier.
timeframe Refines the scope of events to be included in this step, based on when the event occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in this step, based on event property values.
timezone Assigns a timezone offset to relative timeframes.

Special Parameters

Response when with_actors is true

{
  "result": [
    3,
    1,
    0
  ],
  "actors": [
    [ "f9332409s0", "b7732409s0", "k22315b211" ],
    [ "f9332409s0" ],
    null
  ],
  "steps": [
    {
      "actor_property": "visitor.guid",
      "event_collection": "signed up",
      "timeframe": "this_7_days"
    },
    {
      "actor_property": "user.guid",
      "event_collection": "completed profile",
      "timeframe": "this_7_days"
    },
    {
      "actor_property": "user.guid",
      "event_collection": "referred user",
      "timeframe": "this_7_days"
    }
  ]
}
Parameter Default Description
inverted false A boolean value that excludes events matching this step.
optional false A boolean value that instructs the funnel to ignore the effects of this step on subsequent steps.
with_actors false A boolean value that instructs the funnel to return a list of actor_property values for this step.

Query Parameters

Filters

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
        \"event_collection\": \"COLLECTION_NAME\",
        \"timeframe\": \"this_14_days\",
        \"filters\": [
          {
            \"property_name\" : \"price\",
            \"operator\" : \"gte\",
            \"property_value\" : 0.99
          },
          {
            \"property_name\" : \"on_sale\",
            \"operator\" : \"eq\",
            \"property_value\" : true
          }
        ]
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('extraction', {
    event_collection: 'purchases',
    filters: [
      {
        property_name: 'item.price',
        operator: 'gt',
        property_value: 10
      }
    ],
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => "this_14_days", :"filters" => [
  {
    "property_name" => "item.price",
    "operator" => "gt",
    "property_value" => 10
  }
])
<?php

$filters = [
    ["property_name" => "item.price", "operator" => "gt", "property_value" => 10]
];

$client->count("purchases", ["filters" => $filters, "timeframe" => "this_14_days"]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.COUNT)
        .withEventCollection("<event_collection>")
        .withFilter("click-count", FilterOperator.GREATER_THAN, 1)
        .withFilter("click-count", FilterOperator.LESS_THAN, 5)
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();

QueryResult result = queryClient.execute(query);
if (result.isLong()) {
    long queryResult = result.longValue();
}
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();

var filters = new List<QueryFilter>()
{
    new QueryFilter("field1", QueryFilter.FilterOperator.GreaterThan(), "1")
};

var result = keenClient.Query(QueryType.Count(), "user_registrations", relativeTimeframe, filters: filters);

Filters refine the scope of events to be included in an analysis, based on event property values. For example, a filter could be used to limit analysis to events that came from Android users.

Filters are sent as an array of JSON objects. Each JSON object has three properties, all of which are required:

Property Description
property_name Specifies the name of the property to filter.
operator Specifies the filter operator to use.
property_value The value to compare to the property specified by the property_name.

Operator Definitions

Operator Description
eq “Equal to” – Note that if your property’s value is an array, “eq” can be used to filter for values inside that array. For example, eq: 5 will match a value of [5, 6, 7].
ne “Not equal to”
lt “Less than”
lte “Less than or equal to”
gt “Greater than”
gte “Greater than or equal to”
exists Whether or not a specific property exists on an event record. The value passed in must be either true or false.
in Whether or not the property value is in a given set of values. The value passed in must be a JSON array of values. Example: [1,2,4,5].
contains Whether or not the string property value contains the given sequence of characters.
not_contains Whether or not the string property value does not contain the given sequence of characters.
within Used to select events within a certain radius of the provided geo coordinate (for geo analysis only).

Not all filter operators make sense for different property data types, so only certain operators are valid for each.

Note: For contains and not_contains operators, the property must exist in order for the event to pass the filter. If the property does not exist, then the event will not pass the filter and will not be returned as part of the result set.

Data Type Valid Operators
string eq, ne, lt, gt, exists, in, contains, not_contains
number eq, ne, lt, lte, gt, gte, exists, in
boolean eq, exists, in
geo coordinates within

Geo Filtering

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_14_days\",
      \"filters\": [
        {
          \"property_name\" : \"keen.location.coordinates\",
          \"operator\" : \"within\",
          \"property_value\" : {
            \"coordinates\":[ -122.42005, 37.77479 ],
            \"max_distance_miles\": 30
          }
        }
      ]
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'purchases',
    filters: [
      {
        property_name: 'keen.location.coordinates',
        operator: 'within',
        property_value: {
          coordinates: [ -122.42005, 37.77479 ],
          max_distance_miles: 30
        }
      }
    ],
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });

If your events have geo coordinates, you can filter for results within a given radius of a given location. For example, filter events within a ten mile radius of San Francisco [-122.42005, 37.77479].

Please note that the filter takes the format longitude followed by latitude. This is a GeoJSON Standard. It’s the reverse of the order, latitude and longitude, which are displayed in many other contexts, such as Google Maps.

You can combine geo filters with other filters. The example to the right shows how you find events related to developers within 30 miles of San Francisco.

In order for geo-filtering to work, your coordinates must be recorded in the property keen.location or in your specified output property from the IP to Geo enrichment addon.

There is one limitation to geo filtering, which is that it can’t be used in combination with a Group By request.

Group By

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "event_collection": "user_logins",
      "timeframe": "this_14_days",
      "group_by": "user.email"
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    group_by: 'user.email',
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => "this_14_days", :"group_by" => "user.email")
keen.count("purchases", timeframe="this_14_days", group_by="user.email")
<?php

$client->count("clicks", ["group_by" => "user.email", "timeframe" => "this_14_days"]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.COUNT)
        .withEventCollection("<event_collection>")
        .withGroupBy("click-number")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);
if (result.isGroupResult()) {
    for (Map.Entry<Group, QueryResult> groupResult : result.getGroupResults().entrySet()) {
        Map<String, Object> groupProperies = groupResult.getKey().getProperties();
        long groupCount = groupResult.getValue().longValue();
        // ... do something with the group properties and the count result
    }
}
// Supported by this SDK.

Response

{
  "result": [
    {
      "user.email": "ryan@keen.io",
      "result": 39
    },
    {
      "user.email": "dan@keen.io",
      "result": 27
    },
    {
      "user.email": "kirk@keen.io",
      "result": 32
    }
  ]
}

The group_by parameter groups results categorically, by co-occurrence of a specified property.

This type of analysis can help answer questions such as:

  • Of our signups, how many came from iOS, Android, or Web clients?
  • How much revenue has been made for each of our user cohorts?
  • How many users are coming from different countries around the world?

This parameter can be added to any of the following types of analysis:

Adding the group_by parameter changes the structure of the response from a single value to an array of objects containing:

  1. The unique value for the specified group_by property
  2. The result of the analysis


Grouping By Multiple Properties

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "event_collection": "purchases",
      "timeframe": "this_14_days",
      "group_by": [ "item.name", "item.type" ]
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'purchases',
    group_by: [ 'item.name', 'item.type' ],
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => "this_14_days", :group_by => ["item.name", "item.type"])
keen.count("purchases", timeframe="this_14_days", group_by=["item.name", "item.type"])
<?php

$client->count("clicks", ["group_by" => ["item.name", "item.type"]]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.COUNT)
        .withEventCollection("<event_collection>")
        .withGroupBy("click-number")
        .withGroupBy("company-id")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);
if (result.isGroupResult()) {
    for (Map.Entry<Group, QueryResult> groupResult : result.getGroupResults().entrySet()) {
        Map<String, Object> groupProperies = groupResult.getKey().getProperties();
        long groupCount = groupResult.getValue().longValue();
        // ... do something with the group properties and the count result
    }
}
// Supported by this SDK.

Response

{
  "result": [
    {
      "item.name": "Golden Widget",
      "item.type": "Widget",
      "result": 39
    },
    {
      "item.name": "Silver Widget",
      "item.type": "Widget",
      "result": 59
    },
    {
      "item.name": "Milk Shake",
      "item.type": "Food",
      "result": 86
    }
  ]
}

Specify more than one property to group by, with a URL-encoded JSON array of property names.

The response structure for this request simply contains an additional property: the unique value for the second specified group_by property name.


Grouping By Properties at Intervals

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "event_collection": "user logins",
      "group_by": "user.email",
      "timeframe": "previous_3_days",
      "interval": "daily"
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    group_by: 'user.email',
    interval: 'daily',
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :group_by => "user.email", :interval => "daily", :timeframe => "previous_3_days")
keen.count("purchases", group_by="user.email", interval="daily", timeframe="previous_3_days")
<?php

$client->count("clicks", ["group_by" => "user.email", "interval" => "daily", "timeframe" => "previous_3_days"]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.COUNT)
        .withEventCollection("<event_collection>")
        .withInterval("weekly")
        .withGroupBy("click-number")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);

if (result.isIntervalResult()) {
    for (IntervalResultValue intervalResult : result.getIntervalResults()) {
        AbsoluteTimeframe timeframe = intervalResult.getTimeframe();

        for (Map.Entry<Group, QueryResult> groupResult : intervalResult.getResult().getGroupResults().entrySet()) {
            Map<String, Object> groupProperies = groupResult.getKey().getProperties();
            long groupCount = groupResult.getValue().longValue();
            // ... do something with the group properties and the count result
        }
    }
}
// Supported by this SDK.

Response

{
  "result": [
    {
      "timeframe": {
        "start": "2014-08-22T00:00:00.000Z",
        "end": "2014-08-23T00:00:00.000Z"
      },
      "value": [
        {
          "user.email": "ryan@keen.io",
          "result": 3
        },
        {
          "user.email": "dan@keen.io",
          "result": 2
        },
        {
          "user.email": "kirk@keen.io",
          "result": 1
        }
      ]
    },
    {
      "timeframe": {
        "start": "2014-08-23T00:00:00.000Z",
        "end": "2014-08-24T00:00:00.000Z"
      },
      "value": [
        {
          "user.email": "ryan@keen.io",
          "result": 0
        },
        {
          "user.email": "dan@keen.io",
          "result": 1
        },
        {
          "user.email": "kirk@keen.io",
          "result": 1
        }
      ]
    },
    {
      "timeframe": {
        "start": "2014-08-25T00:00:00.000Z",
        "end": "2014-08-26T00:00:00.000Z"
      },
      "value": [
        {
          "user.email": "ryan@keen.io",
          "result": 5
        },
        {
          "user.email": "dan@keen.io",
          "result": 4
        },
        {
          "user.email": "kirk@keen.io",
          "result": 0
        }
      ]
    }
  ]
}

Groups results by a property at specified intervals by using group_by in conjunction with the interval parameter (detailed below).

Using these parameters together alters the response structure. Each sub-timeframe (which is determined by the interval attribute) has a response similar to that of a basic group_by, containing the unique value of the property as well as the result of the analysis.

Interval

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d '{
      "event_collection": "user logins",
      "timeframe": "previous_3_days",
      "interval": "daily"
    }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    interval: 'daily',
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :interval => "daily", :timeframe => "previous_3_days")
keen.count("purchases", interval="daily", :timeframe="previous_3_days")
<?php

$client->count("clicks", ["interval" => "daily", "timeframe" => "previous_3_days"]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.COUNT)
        .withEventCollection("<event_collection>")
        .withInterval("weekly")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);
if (result.isIntervalResult()) {
    for (IntervalResultValue intervalResult : result.getIntervalResults()) {
        AbsoluteTimeframe timeframe = intervalResult.getTimeframe();
        long intervalCount = intervalResult.getResult().longValue();
        // ... do something with the absolute timeframe and count result.
    }
}
// Supported by this SDK.

Response

{
  "result": [
    {
      "timeframe": {
        "start": "2014-08-22T00:00:00.000Z",
        "end": "2014-08-23T00:00:00.000Z"
      },
      "value": 6
    },
    {
      "timeframe": {
        "start": "2014-08-23T00:00:00.000Z",
        "end": "2014-08-24T00:00:00.000Z"
      },
      "value": 2
    },
    {
      "timeframe": {
        "start": "2014-08-25T00:00:00.000Z",
        "end": "2014-08-26T00:00:00.000Z"
      },
      "value": 9
    }
  ]
}

The interval parameter groups results into sub-timeframes spanning a specified length of time.

This type of analysis can help answer questions such as:

  • How many signups have occurred daily, over the past 21 days?
  • How much has revenue grown per week since launching a new product?

Supported Intervals

  • minutely
  • hourly
  • daily
  • weekly
  • monthly
  • yearly

Custom Intervals

In addition to the above intervals, the following pattern can be used to create highly specific custom intervals: every_{n}_{units}, where {n} can be any whole integer greater than 0 (zero), and {units} can be minutes, hours, days, weeks, months, or years.

Here are a few examples:

  • every_30_minutes
  • every_8_hours
  • every_3_days
  • every_2_weeks
  • every_6_months
  • every_3_years

Timeframe

The timeframe parameter specifies a period of time over which to run an analysis. This refines the scope of events that are included in the analysis, based on when each event occurred.

There are two types of timeframes:

  • Absolute timeframes: a fixed timeframe with an explicit start and end
  • Relative timeframes: a rolling timeframe that is relative to “now”


Absolute Timeframes

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": {
        \"start\": \"2012-08-13T19:00:00.000Z\",
        \"end\": \"2013-09-20T19:00:00.000Z\"
      }
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    timeframe: {
      start: '2012-08-15T19:00:00.000Z',
      end: '2017-08-15T19:00:00.000Z'
    }
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => {
  :start => "2012-08-13T19:00:00.000Z",
  :end => "2013-09-20T19:00:00.000Z"
})
keen.count("purchases", timeframe={
  "start": "2012-08-13T19:00:00.000Z",
  "end": "2013-09-20T19:00:00.000Z"
})
<?php

$client->count('clicks', ["timeframe" => [
  "start" => "2012-08-13T19:00:00.000Z",
  "end" => "2013-09-20T19:00:00.000Z"
]]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
long countUnique = queryClient.countUnique("<event_collection>", "<target_property>", new AbsoluteTimeframe("2015-05-15T19:00:00.000Z","2015-06-07T19:00:00.000Z"));
var absoluteTimeframe = new QueryAbsoluteTimeframe(DateTime.Now.AddMonths(-1), DateTime.Now));

var countUnique = keenClient.Query(QueryType.CountUnique(), "target_collection", "target_property", absoluteTimeframe);

Absolute timeframes are passed in with a URL-encoded JSON object containing “start” and “end” properties with ISO-8601 formatted date strings.

A query will be inclusive of events starting at the exact same time as the start time and exclusive of events starting with the exact same time as the end time. In other words, to run a query on an exact 24 hour window, you can use a timeframe that starts at midnight one day and ends at midnight the next day.


Relative Timeframes

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => "this_7_days")
keen.count("purchases", timeframe="this_7_days")
<?php

$client->count("clicks", ["timeframe" => "this_7_days"]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
long count = queryClient.count("<event_collection>", new RelativeTimeframe("this_week"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();

var countUnique = keenClient.Query(QueryType.CountUnique(), "target_collection", "target_property", relativeTimeframe);

Relative timeframes are passed in with a patterned string sequence:

{rel}_{n}_{units}

Pattern Description
{rel} “this” or “previous” – Use “this” when you want to include events happening right up until now. Use “previous” when you only want to get results for complete chunks of time (e.g. the full hour, day, or week).
{n} Any whole number greater than 0 (zero).
{units} “minutes”, “hours”, “days”, “weeks”, “months”, or “years”.

This chart illustrates the difference between “this” and “previous”:

Relative timeframe illustration

Notice that this_2_days will include all of the current day and all of the previous day, whereas previous_2_days includes the previous two fully completed days and none of the current day.

Below are the supported relative timeframes for “this”:

Timeframe Description
this_minute Creates a timeframe starting from the beginning of the current minute until now.
this_hour Creates a timeframe starting from the beginning of the current hour until now.
this_day Creates a timeframe starting from the beginning of the current day until now.
this_week Creates a timeframe starting from the beginning of the current week until now.
this_month Creates a timeframe starting from the beginning of the current month until now.
this_year Creates a timeframe starting from the beginning of the current year until now.
this_n_minutes All of the current minute and the previous completed n-1 minutes.
this_n_hours All of the current hour and the previous completed n-1 hours.
this_n_days All of the current day and the previous completed n-1 days.
this_n_weeks All of the current week and the previous completed n-1 weeks.
this_n_months All the current month and previous completed n-1 months.
this_n_years All the current year and previous completed n-1 years.

Below are the supported relative timeframes for “previous”:

Timeframe Description
previous_n_minutes Gives a start of n-minutes before the most recent complete minute and an end at the most recent complete minute. (For example: If right now it is 7:15:30pm and I specify “previous_3_minutes”, the timeframe would stretch from 7:12pm until 7:15pm.)
previous_n_hours Gives a start of n-hours before the most recent complete hour and an end at the most recent complete hour. (For example: If right now it is 7:15pm and I specify “previous_7_hours”, the timeframe would stretch from noon until 7:00pm.)
previous_n_days Gives a starting point of n-days before the most recent complete day and an end at the most recent complete day. (For example: If right now it is Friday at 9:00am and I specify a timeframe of “previous_3_days”, the timeframe would stretch from Tuesday morning at 12:00am until Thursday night at midnight.)
previous_n_weeks Gives a start of n-weeks before the most recent complete week and an end at the most recent complete week. (For example: If right now it is Monday, and I specify a timeframe of “previous_2_weeks”, the timeframe would stretch from three Sunday mornings ago at 12:00am until the most recent Sunday at 12:00am (yesterday morning).)
previous_n_months Gives a start of n-months before the most recent completed month and an end at the most recent completed month. (For example: If right now is the 5th of the month, and I specify a timeframe of “previous_2_months”, the timeframe would stretch from the start of two months ago until the end of last month.)
previous_n_years Gives a start of n-years before the most recent completed year and an end at the most recent completed year. (For example: If right now is the June 5th, and I specify a timeframe of “previous_2_years”, the timeframe would stretch from the start of two years ago until the end of last year.)

Below are several short-hand convenience names:

Timeframe Description
today convenience for “this_day”
previous_minute convenience for “previous_1_minute”
previous_hour convenience for “previous_1_hour”
yesterday convenience for “previous_1_day”
previous_day convenience for “previous_1_day”
previous_week convenience for “previous_1_week”
previous_month convenience for “previous_1_months”
previous_year convenience for “previous_1_years”

Timezone

The timezone parameter is available when running an analysis with a relative timeframes.

Timezones ensure you’re getting the data from your local definition of “today”, rather than UTC. This helps make dashboards and visualizations relevant to whomever is looking at them, regardless of where they are in the world.

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_7_days\",
      \"timezone\": -28800
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    timeframe: 'this_7_days',
    timezone: -28800
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.count("purchases", :timeframe => "this_7_days", :timezone => -28800)
keen.count("purchases", timeframe="this_7_days", timezone=-28800)
<?php

$client->count("clicks", ["timeframe" => "this_7_days", "timezone" => -28800]);

?>
// Supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// Timezone parameter is built into RelativeTimeframe
long count = queryClient.count("<event_collection>", new RelativeTimeframe("this_week", "US/Pacific"));
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var countUnique = keenClient.Query(QueryType.CountUnique(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

To specify a timezone, just include a timezone parameter with a value equal to the number of seconds to offset the time.

For example, to adjust an analysis to US/Pacific time (UTC-08:00), set the timezone parameter to -28800 (-8 hours * 60 minutes * 60 seconds).

Named Timezones

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_7_days\",
      \"timezone\": \"US/Pacific\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('count', {
    event_collection: 'logins',
    timeframe: 'this_7_days',
    timezone: 'US/Pacific'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });

Keen IO’s API supports all timezones part of the tz database.

It’s best to use these when you can because you no longer have to worry about Daylight Savings Time (DST).

To use a specific timezone, simply set the timezone parameter to a string containing the named timezone. Here are some examples of named timezones:

Timezone Description
US/Eastern The Eastern timezone in the U.S., either standard or daylight.
US/Central The Central timezone in the U.S., either standard or daylight.
US/Mountain The Mountain timezone in the U.S., either standard or daylight.
US/Pacific The Pacific timezone in the U.S., either standard or daylight.
US/Alaska The Alaskan timezone in the U.S., either standard or daylight.
US/Hawaii The Hawaiian timezone in the U.S. Hawaii doesn’t observe DST, pretty easy for us!
Europe/Amsterdam The timezone for Amsterdam in Europe, either standard or daylight.
Europe/London The timezone for London in Europe, either standard or daylight.
Europe/Paris The timezone for Paris in Europe, either standard or daylight.
Europe/Prague The timezone for Prague in Europe, either standard or daylight.
Europe/Stockholm The timezone for Stockholm in Europe, either standard or daylight.
Europe/Copenhagen The timezone for Copenhagen in Europe, either standard or daylight.
Africa/Casablanca The timezone for Casablanca in Africa, either standard or daylight.
Africa/Nairobi The timezone for Nairobi in Africa. No daylight savings observance.
Asia/Singapore The timezone for Singapore in Asia, either standard or daylight.
Australia/Sydney The timezone for Sydney in Australia, either standard or daylight.
Asia/Dubai The timezone for Dubai in Asia, either standard or daylight.
Asia/Istanbul The timezone for Istanbul in Asia, either standard or daylight.
Asia/Jakarta The timezone for Jakarta in Asia, either standard or daylight.
Asia/Tokyo The timezone for Tokyo in Asia, either standard or daylight.
America/Sao_Paulo The timezone for Sao Paulo in South America, either standard or daylight.
Australia/Perth The timezone for Perth in Australia, either standard or daylight.
Europe/Istanbul The timezone for Istanbul in Europe, either standard or daylight.
Pacific/Auckland The timezone for Auckland in New Zealand, either standard or daylight.
UTC The UTC timezone.

Saved Queries

Saved queries allow you to easily access your favorite metrics. Rather than entering the same query parameters over and over again, queries can be easily saved, edited, and shared with your teammates.

Creating a Saved Query

Request

# PUT
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -X PUT \
    -d '{
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timeframe": "this_2_weeks"
    }
}'
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const savedQueryName = 'my-saved-query';

client
  .put(client.url('queries', 'saved', savedQueryName))
  .auth(client.masterKey())
  .send({
    query: {
      analysis_type: 'count',
      event_collection: 'purchases',
      timeframe: 'this_2_weeks',
      filters: [
        {
          property_name: 'price',
          operator: 'gte',
          property_value: 1.00
        }
      ]
    },
    metadata: {
      display_name: 'Purchases (past 2 weeks)',
    },
    refresh_rate: 0
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
saved_query_attributes = {
  refresh_rate: 0,
  query: {
    analysis_type: "count",
    event_collection: "purchases",
    timeframe: "this_2_weeks",
    filters: [
      {
        property_name: "price",
        operator: "gte",
        property_value: 1.00
      }
    ]
  },
  metadata: {
    display_name: "Purchases (past 2 weeks)",
  }
}
Keen.saved_queries.create("saved-query-name", saved_query_attributes)
saved_query_attributes = {
  "refresh_rate": 0,
  "query": {
    "analysis_type": "count",
    "event_collection": "purchases",
    "timeframe": "this_2_weeks",
    "filters": [
      {
        "property_name": "price",
        "operator": "gte",
        "property_value": 1.00
      }
    ]
  },
  "metadata": {
    "display_name": "Purchases (past 2 weeks)",
  }
}
keen.saved_queries.create("saved-query-name", saved_query_attributes)
<?php

$client = KeenIOClient::factory([
  'projectID' => $project_id,
  'masterKey' => $master_key
]);

$query = [
    "analysis_type" => "count",
    "event_collection" => "purchases",
    "filters" =>
        [
            [
                "property_name" => "price",
                "operator" => "gte",
                "property_value" => 1.00
            ]
        ],
    "timeframe" => "this_2_weeks"
];

$client->createSavedQuery(['query_name' => 'saved-query-name', 'query' => $query]);

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// query
SingleAnalysis count = new SingleAnalysis.Builder(QueryType.COUNT)
        .withEventCollection("purchases")
        .withFilter("price", FilterOperator.GREATER_THAN_EQUAL, 1.00)
        .withTimeframe(new RelativeTimeframe("this_2_weeks"))
        .build();
// Currently not supported by this SDK.

Response

{
    "query_name": "QUERY_NAME",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_2_weeks",
        "interval": null,
        "group_by": null
    },
    "refresh_rate": 14400,
    "run_information": null,
    "created_date": "2015-08-27T04:09:26.309972+00:00",
    "last_modified_date": "2015-08-27T04:09:26.309972+00:00",
    "created": true,
    "updated": false,
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME/result"
    }
}

# Note: run_information is null because the query is currently being scheduled and executed.  Once the initial scheduling and execution has been completed, run_information will be populated.

HTTP Methods

Method Authentication Description
PUT Master Key Create a saved query

Required Parameters

Parameter Description
query Parameters for the query you want to save and have us run on an interval

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.

Getting Saved Query Results

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json'
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('saved', 'my-saved-query')
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
Keen.saved_queries.get("saved-query-slug", results: true)
keen.saved_queries.results("saved-query-slug")
<?php

$client = KeenIOClient::factory([
  'projectId' => $project_id,
  'masterKey' => $master_key
]);

$results = $client->getSavedQueryResults(['query_name' => 'saved_query_name']);

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// query
QueryResult sumResultSaved = savedQueryApi.getResult("saved-query-name");
// Currently not supported by this SDK.

Response

{
    "query_name": "QUERY_NAME",
    "result": 880,
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_2_weeks",
        "interval": null,
        "group_by": null
    },
    "refresh_rate": 14400,
    "run_information": {
        "last_run_date": "2015-08-27T04:32:56.795038",
        "last_run_message": "success",
        "last_run_status": 200,
        "next_run_date": "2015-08-27T08:32:56.795038"
    },
    "created_date": "2015-08-27T04:32:48.454000+00:00",
    "last_modified_date": "2015-08-26T23:32:56.838000+00:00",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME/result"
    }
}

HTTP Methods

Method Authentication Description
GET Read Key Get saved query results

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.

Updating Saved Queries

Request

# PUT
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -X PUT
    -d '{
            "query_name": "NEW_QUERY_NAME",
            "query": {
                "analysis_type": "count",
                "event_collection" : "purchases",
                "filters": [
                    {
                        "property_name": "price",
                        "operator": "gte",
                        "property_value": 1.00
                    }
                ],
                "timeframe": "this_1_weeks"
            }
        }'
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .put(client.url('queries', 'saved', 'my-saved-query'))
  .auth(client.masterKey())
  .send({
    query_name: 'purchases-past-week',
    query: {
      timeframe: 'this_1_weeks'
    },
    metadata: {
      display_name: 'Purchases (past week)',
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
saved_query_attributes = {
  metadata: {
    display_name: "New Display Name",
  }
}
Keen.saved_queries.update("saved-query-name", saved_query_attributes)
"saved_query_attributes" = {
  "metadata": {
    "display_name": "New Display Name",
  }
}
keen.saved_queries.update("saved-query-name", saved_query_attributes)
<?php

$client = KeenIOClient::factory([
  'projectID' => $project_id,
  'masterKey' => $master_key
]);

$query = [
    "analysis_type" => "count",
    "event_collection" => "purchases",
    "filters" =>
        [
            [
                "property_name" => "price",
                "operator" => "gte",
                "property_value" => 2.50
            ]
        ],
    "timeframe" => "this_1_weeks"
];

$client->updateSavedQuery(['query_name' => 'saved-query-name', 'query' => $query]);

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// query
Map<String, Object> updateResponse = null;

// Update a saved query to now be a cached query with the minimum refresh rate of 4 hrs...

// ...using partial update:
Map<String, Object> partialUpdates = new HashMap<String, Object>();
int refreshRate = 4 * 3600; // 4 hrs
partialUpdates.put("refresh_rate", refreshRate);

updateResponse = savedQueryApi.updateQuery("saved-query-name", partialUpdates);

// ...using full update, if we've already fetched the query definition and removed unacceptable
// properties. Some properties, like "run_information" returned by getting a query definition cannot
// be `PUT` back or an error is returned.:
Map<String, Object> fullUpdates = mySanitizeHelper(countQueryDef);
fullUpdates.put("refresh_rate", 14400);
updateResponse = savedQueryApi.updateQueryFull("saved-query-name", fullUpdates);

// ...or using the helpers:
updateResponse = savedQueryApi.setRefreshRate("saved-query-name", RefreshRate.fromHours(4));
// Currently not supported by this SDK.

Response

{
    "query_name": "NEW_QUERY_NAME",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "group_by": null
    },
    "created_date": "2015-08-27T04:09:26.309972+00:00",
    "last_modified_date": "2015-08-27T04:09:26.309972+00:00",
    "created": true,
    "updated": false,
    "urls": {
      "saved_query_results_url": "/3.0/projects/PROJECT_ID/saved_queries/my_saved_query/result",
      "saved_query_url": "/3.0/projects/PROJECT_ID/saved_queries/my_saved_query"
    }
}

To change the definition of the query, you must provide a complete new definition for the query in the “query” field in the body.

To rename a query, simply provide a “query_name” field in the request body with the new name. Note, this changes the URL for all requests going forward.

You can provide either one or both.

HTTP Methods

Method Authentication Description
PUT Master Key Update a saved query

Optional Parameters

Parameter Description
query_name Change the name of the query
query Parameters for the query you want to save and have us run on an interval

Getting All Saved Query Definitions

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json'
// Currently not supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('queries', 'saved'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
Keen.saved_queries.all
keen.saved_queries.all()
<?php

$client = KeenIOClient::factory([
  'projectID' => $project_id,
  'masterKey' => $master_key    
]);   

$results = $client->getSavedQueries();

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// query
List<Map<String, Object>> allQueryDefs = savedQueryApi.getAllDefinitions();
// Currently not supported by this SDK.

Response

[{
    "query_name": "QUERY_NAME",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "group_by": null
    },
    "refresh_rate": 14400,
    "run_information": {
        "last_run_date": "2015-08-27T04:32:56.795038",
        "last_run_message": "success",
        "last_run_status": 200,
        "next_run_date": "2015-08-27T08:32:56.795038"
    },
    "created_date": "2015-08-27T04:32:48.454000+00:00",
    "last_modified_date": "2015-08-26T23:32:56.838000+00:00",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME/result"
    }
}]

HTTP Methods

Method Authentication Description
GET Master Key Get a saved query definition

Getting a Saved Query Definition

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json'
// Currently not supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('queries', 'saved', 'my-saved-query'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
Keen.saved_queries.get("saved-query-name")
keen.saved_queries.get("saved-query-slug")
<?php

$client = KeenIOClient::factory([
    'projectId' => $project_id,
    'masterKey' => $master_key
]);

$results = $client->getSavedQuery(['query_name' => 'saved_query_name']);

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// query
Map<String, Object> countQueryDef = savedQueryApi.getDefinition("saved-query-name");
// Currently not supported by this SDK.

Response

{
    "query_name": "QUERY_NAME",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "group_by": null
    },
    "refresh_rate": 14400,
    "run_information": {
        "last_run_date": "2015-08-27T04:32:56.795038",
        "last_run_message": "success",
        "last_run_status": 200,
        "next_run_date": "2015-08-27T08:32:56.795038"
    },
    "created_date": "2015-08-27T04:32:48.454000+00:00",
    "last_modified_date": "2015-08-26T23:32:56.838000+00:00",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME/result"
    }
}

HTTP Methods

Method Authentication Description
GET Master Key Get a saved query definition

Delete a Saved Query

Request

# DELETE
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -X DELETE
// Currently not supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .del(client.url('queries', 'saved', 'my-saved-query'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
Keen.saved_queries.delete("saved-query-slug")
keen.saved_queries.delete("saved-query-slug")
<?php

$client = KeenIOClient::factory([
    'projectId' => $project_id,
    'masterKey' => $master_key
]);

$client->deleteSavedQuery(['query_name' => 'saved_query_name']);

?>
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// delete 
savedQueryApi.deleteQuery("saved-query-name");
// Currently not supported by this SDK.

Response

// Successful result
204 - No Content

HTTP Methods

Method Authentication Description
DELETE Master Key Delete a saved query

Cached Queries

Cached Queries are a way for you to build applications with charts and tables that load instantly, even as your data volume grows. Cached Queries reduce your average query response time down to milliseconds, which is key for building user-facing reports where the demand for responsiveness is highest.

A Cached Query is simply a Keen Query that you specify to be recomputed on a regular interval. Once you identify the queries you want to build on, add caching and we will take care of running them in the background. Then when you want the answers, we will have them ready to go instead of calculating them from scratch.

As an example: you can cache a query like “Median response time over the last 90 days, updated hourly” and we’ll continuously refresh the cached result for you based on the hourly “refresh_rate”. Everytime a user loads your report, it will only take milliseconds to retrieve the median over three months of data.

Try caching a Multi-Analysis Query to get several results in a single rapid response! If you want to cache results, but retrieve them based on an index like Customer ID or allow a user to request a custom set of intervals then check out Cached Datasets

Query Caching can be enabled on any Saved Query

Creating a Cached Query

Request

# PUT
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -X PUT
    -d '{
            "refresh_rate": 14400
        }'
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .put(client.url('queries', 'saved', 'my-saved-query'))
  .auth(client.masterKey())
  .send({
    refresh_rate: 14400
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// To work with Saved/Cached Queries, create a KeenQueryClient as normal, then use it to create a SavedQueries implementation

KeenQueryClient queryClient = ...;
SavedQueries savedQueryApi = queryClient.getSavedQueriesInterface();

// First, we'll create a query. Let's count the number of purchases with a price >= $1 in the last
// two weeks including the current week.
SingleAnalysis count = new SingleAnalysis.Builder(QueryType.COUNT)
        .withEventCollection("purchases")
        .withFilter("price", FilterOperator.GREATER_THAN_EQUAL, 1.00)
        .withTimeframe(new RelativeTimeframe("this_2_weeks"))
        .build();

// There are variations of create*Query() to set things like the refresh rate.
Map<String, Object> rawCreateResponse = savedQueryApi.createSavedQuery("saved-query-name", count);
// Currently not supported by this SDK.

Response

{
    "query_name": "QUERY_NAME",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
            }
        ],
        "timezone": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "group_by": null
    },
    "refresh_rate": 14400,
    "run_information": null,
    "created_date": "2015-08-27T04:09:26.309972+00:00",
    "last_modified_date": "2015-08-27T04:09:26.309972+00:00",
    "created": true,
    "updated": false,
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/cached_queries/QUERY_NAME/result"
    }
}

By turning on caching, you can tell Keen to automatically refresh your saved queries so you can get immediate results. We will calculate the results of your query on a set interval, and have them available for you immediately when you ask for the Saved Query result.

To turn on caching, simply update the saved query, and include a refresh_rate property.

The refresh rate value is in seconds, and must be between 4 hours (14,400 seconds) and 24 hours (86,400 seconds).

Cached Datasets

Note: Cached Datasets are currently in Early Release.

Cached Datasets are a powerful way for you to build applications with charts and tables that load instantly, even as your Streams volume grows. Conceptually similar to Cached Queries, a Cached Dataset additionally allows you to retrieve results indexed by properties like customer, cohort, article, campaign and more. You can also create a Cached Dataset over a large timeframe like “365 days, daily” and then retrieve results for any arbitrary timeframes contained in that Cached Dataset.

Some example cases where you’d want to use Cached Datasets:

  1. You want your dashboard to retrieve the appropriate results for each user who loads the report
  2. You want to empower the user to change the timeframe from the last 14 days to 3 hours on Tuesday seamlessly
  3. You want to send an email to each user who viewed a product 3 times in the last week
  4. You want to build an automated job that generates account bills for each account’s monthly usage

Creating a Cached Dataset

You can create a Cached Dataset by providing the query you want to cache, the property name you want to index by, and a display name for reference. The timeframe of the query will determine how much data will be kept cached and the interval determines the granularity of results. Results will be cached for each index value in each interval in the timeframe.

Currently Cached Dataset query definitions require Relative Timeframes and the unit must match the interval, eg. this_90_days and daily or this_12_months and monthly.

Try caching a Multi-Analysis Query to get several results in a single rapid response!

Funnels are not currently supported by Cached Datasets, but funnels do work with Cached Queries.

Request

# PUT
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/datasets/DATASET_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -X PUT \
    -d '{
    "display_name": "Count Daily Product Purchases Over $100 by Country",
    "query": {
        "analysis_type": "count",
        "event_collection" : "purchases",
        "filters": [
            {
                "property_name": "price",
                "operator": "gte",
                "property_value": 100
            }
        ],
        "timeframe": "this_500_days",
        "interval": "daily",
        "group_by": "ip_geo_info.country"
    },
    "index_by": "product.id"
}'
// Currently not supported by this SDK
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const newDatasetName = 'my-first-dataset';

client
  .put(client.url('datasets', newDatasetName))
  .auth(client.masterKey())
  .send({
    display_name: 'Count Daily Product Purchases Over $100 by Country',
    query: {
      analysis_type: 'count',
      event_collection: 'purchases',
      filters: [
        {
          property_name: price,
          operator: gte,
          property_value: 100
        }
      ],
      group_by: 'ip_geo_info.country',
      interval: 'daily',
      timeframe: 'this_500_days'
    },
    index_by: 'product.id'
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response


{
  "project_id":"PROJECT ID",
  "organization_id":"ORGANIZATION",
  "dataset_name":"DATASET NAME",
  "display_name":"Count Daily Product Purchases Over $100 by Country",
  "query": {
    "project_id":"PROJECT ID",
    "analysis_type":"count",
    "event_collection":"purchases",
    "filters": [
      {
        "property_name":"price",
        "operator":"gte",
        "property_value":100
      }
    ],
    "timeframe":"this_500_days",
    "timezone":null,
    "interval":"daily",
    "group_by":["ip_geo_info.country"]
  },
  "index_by": "product.id",
  "last_scheduled_date":"1970-01-01T00:00:00.000Z",
  "latest_subtimeframe_available":"1970-01-01T00:00:00.000Z",
  "milliseconds_behind": 3600000
}

HTTP Methods

Method Authentication Description
PUT Master Key Create a Cached Dataset. Updates are not currently supported.

Required Parameters

Parameter Description
query The query definition you want Keen to optimize for your application. Currently supports Relative Timeframes. Interval should match; eg. this_90_day and daily. Cannot contain group_by on same property as index_by
index_by The event property name containing an identifier, such as user_id or store.id, that will be used to index and retrieve query results. Values for this property must be recorded as strings. Numeric properties cannot be indexed. This value cannot be the same as a group_by property set in the query definition. Currently supports exactly one index_by property name.
display_name The human-readable string name for your Cached Dataset

Getting a Dataset Definition

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/datasets/DATASET_NAME \
    -H "Authorization: READ_KEY"
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('datasets', 'my-first-dataset'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response


{
  "project_id":"5011efa95f546f2ce2000000",
  "organization_id":"4f3846eaa8438d17fb000001",
  "dataset_name":"count-purchases-gte-100-by-country-daily",
  "display_name":"Count Daily Product Purchases Over $100 by Country",
  "query": {
    "project_id":"5011efa95f546f2ce2000000",
    "analysis_type":"count",
    "event_collection":"purchases",
    "filters": [
      {
        "property_name":"price",
        "operator":"gte",
        "property_value":100
      }
    ],
    "timeframe":"this_500_days",
    "timezone":null,
    "interval":"daily",
    "group_by":["ip_geo_info.country"]
  },
  "index_by":["product.id"],
  "last_scheduled_date":"2016-11-04T18:52:36.323Z",
  "latest_subtimeframe_available":"2016-11-05T00:00:00.000Z",
  "milliseconds_behind": 3600000
}

HTTP Methods

Method Authentication Description
GET Read Key Get a Cached Dataset definition

Retrieving results from a Cached Dataset

Once your dataset is up and running, you can retrieve results for specific index_by values and timeframes.

You can retrieve results using any Relative Timeframe that is within the Cached Dataset and matches the timeframe unit in the definition. For example, if the Cached Dataset is defined for “this_365_days” you can retrieve “this_3_days” or “previous_30_days”.

You can also can also use Absolute Timeframes, but the timeframe must resolve to intervals matched by the dataset. For example, requesting "timeframe": {"start": "2016-11-02T **01**:00:00.000Z", "end": "2016-11-02T **04**:00:00.000Z"} ( a three hour window) from a daily dataset won’t resolve but "timeframe": { "start": "2016-11- **02**T00:00:00.000Z", "end": "2016-11- **05**T00:00:00.000Z" } (a three day window) would.

Based on the raw data an index_by value might not have nonzero results for some intervals.

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/datasets/DATASET_NAME/results?api_key=READ_KEY&index_by=INDEX_VALUE&timeframe=TIMEFRAME
// Supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('dataset', {
    name: 'my-first-dataset',
    index_by: 'INDEX_VALUE',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK
# Currently not supported by this SDK
# Currently not supported by this SDK
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// example: just like a normal count query result with a group_by and daily interval
{
  "result": [
    {
      "timeframe": {
        "start": "2016-11-02T00:00:00.000Z",
        "end": "2016-11-03T00:00:00.000Z"
      },
      "value": [
        {
          "item.name": "Golden Widget",
          "result":0
        },
        {
          "item.name": "Silver Widget",
          "result":18
        },
        {
          "item.name": "Bronze Widget",
          "result":1
        },
        {
          "item.name": "Platinum Widget",
          "result":9
        }
      ]
    },
    {
      "timeframe": {
        "start":"2016-11-03T00:00:00.000Z",
        "end":"2016-11-04T00:00:00.000Z"
      },
      "value": [
        {
          "item.name": "Golden Widget",
          "result":1
        },
        {
          "item.name": "Silver Widget",
          "result":13
        },
        {
          "item.name": "Bronze Widget",
          "result":0
        },
        {
          "item.name": "Platinum Widget",
          "result":3
        }
      ]
    }
  ]
}

HTTP Methods

Method Authentication Description
GET Read Key Get query results from a Cached Dataset

Required Parameters

Parameter Description
index_by The event property name containing an identifier, such as user_id or store.id, that will be used to index and retrieve query results. Values for this property must be recorded as strings. Numeric properties cannot be indexed. This value cannot be the same as a group_by property set in the query definition. Currently supports exactly one index_by property name.
timeframe Limits retrieval of results to a specific portion of the Cached Dataset

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header

Listing Cached Dataset Definitions for Project

Request

# GET
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/datasets \
    -H "Authorization: READ_KEY"
// Currently not supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .get(client.url('datasets'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

{
  "datasets": [{
    "project_id": "PROJECT_ID",
    "organization_id": "ORGANIZATION_ID",
    "dataset_name": "DATASET_NAME_1",
    "display_name": "a first dataset wee",
    "query": {
      "project_id": "PROJECT_ID",
      "analysis_type": "count",
      "event_collection": "best collection",
      "filters": [{
        "property_name": "request.foo",
        "operator": "lt",
        "property_value": 300
      }],
      "timeframe": "this_500_hours",
      "timezone": "US/Pacific",
      "interval": "hourly",
      "group_by": [
        "exception.name"
      ]
    },
    "index_by": [
      "project.id"
    ],
    "last_scheduled_date": "2016-11-04T18:03:38.430Z",
    "latest_subtimeframe_available": "2016-11-04T19:00:00.000Z",
    "milliseconds_behind": 3600000
  }, {
    "project_id": "PROJECT_ID",
    "organization_id": "ORGANIZATION_ID",
    "dataset_name": "DATASET_NAME_10",
    "display_name": "tenth dataset wee",
    "query": {
      "project_id": "PROJECT_ID",
      "analysis_type": "count",
      "event_collection": "tenth best collection",
      "filters": [],
      "timeframe": "this_500_days",
      "timezone": "UTC",
      "interval": "daily",
      "group_by": [
        "analysis_type"
      ]
    },
    "index_by": [
      "project.organization.id"
    ],
    "last_scheduled_date": "2016-11-04T19:28:36.639Z",
    "latest_subtimeframe_available": "2016-11-05T00:00:00.000Z",
    "milliseconds_behind": 3600000
  }],
  "next_page_url": "https://api.keen.io/3.0/projects/PROJECT_ID/datasets?limit=LIMIT&after_name=DATASET_NAME_10"
}

HTTP Methods

Method Authentication Description
GET Read Key Get list of Cached Dataset definitions for a project

Optional Parameters

Parameter Description
limit How many Cached Dataset definitions to return at a time (1-100). Defaults to 10.
after_name A cursor for use in pagination. after_name is the Cached Dataset name that defines your place in the list. For instance, if you make a list request and receive 100 Cached Dataset definitions, ending with dataset_foo you can use dataset_foo as your after_name to retrieve the next page of definitions. Lists also return with helper “next_page_url” that uses after_name, so your subsequent call can fetch the next page of the list easily.

Deleting a Cached Dataset

Request

# DELETE
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/datasets/QUERY_NAME \
    -H "Authorization: MASTER_KEY" \
    -X DELETE
// Currently not supported by this SDK.
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .del(client.url('datasets', 'my-first-dataset'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK
# Currently not supported by this SDK
# Currently not supported by this SDK
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// Successful result
204 - No Content

HTTP Methods

Method Authentication Description
DELETE Master Key Delete a Cached Dataset

Query Availability

Request

$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries?api_key=MASTER_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('queries'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });

Response

{
  "count_unique_url": "/3.0/projects/PROJECT_ID/queries/count_unique",
  "count_url": "/3.0/projects/PROJECT_ID/queries/count",
  "extraction_url": "/3.0/projects/PROJECT_ID/queries/extraction",
  "funnel_url": "/3.0/projects/PROJECT_ID/queries/funnel",
  "select_unique_url": "/3.0/projects/PROJECT_ID/queries/select_unique"
}

Return a list of available query resources with a GET request.

HTTP Methods

Method Authentication Response
GET Master Key Returns all available query resources
HEAD Master Key Returns the response header

Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Extractions

Request

# GET
$ curl "https://api.keen.io/3.0/projects/PROJECT_ID/queries/extraction?api_key=READ_KEY&event_collection=COLLECTION_NAME&timeframe=this_7_days"

# POST
$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/extraction \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": \"this_7_days\"
    }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('extraction', {
    event_collection: 'purchases',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
Keen.extraction("purchases", :timeframe => "this_7_days")
# => [{ "price" => 20, ... }, { ... }]
keen.extraction("purchases", timeframe="this_7_days")
# => [{ "price" => 20, ... }, { ... }]
<?php

$client->extraction("purchases", ["timeframe" => "this_7_days"]);
// => [{ "price" => 20, ... }, { ... }]

?>
// Currently not supported by this SDK.
// Add the Keen Query package to your build and then build it
KeenProject queryProject = new KeenProject("<project id>", "<write key>", "<read key>");
KeenQueryClient queryClient = new KeenQueryClient.Builder(queryProject).build();

// query
Query query = new Query.Builder(QueryType.EXTRACTION)
        .withEventCollection("<event_collection>")
        .withTimeframe(new RelativeTimeframe("this_month"))
        .build();
QueryResult result = queryClient.execute(query);
if (result.isListResult()) {
    List<QueryResult> listResults = result.getListResults();
    for (QueryResult item : listResults) {
        if (item.isLong()) {
            // do something with long value
        }
    }
}
var relativeTimeframe = QueryRelativeTimeframe.ThisWeek();
var timezone = "US/Pacific"; // If not specified, timezone defaults to "UTC"

var extraction = keenClient.Query(QueryType.Extraction(), "target_collection", "target_property", relativeTimeframe, timezone: timezone);

Response

{
  "result": [
    {
      "keen": {
        "created_at": "2012-07-30T21:21:46.566000+00:00",
        "timestamp": "2012-07-30T21:21:46.566000+00:00",
        "id": ""
      },
      "user": {
        "email": "dan@keen.io",
        "id": "4f4db6c7777d66ffff000000"
      },
      "user_agent": {
        "browser": "chrome",
        "browser_version": "20.0.1132.57",
        "platform": "macos"
      }
    },
    {
      "keen": {
        "created_at": "2012-07-30T21:40:05.386000+00:00",
        "timestamp": "2012-07-30T21:40:05.386000+00:00",
        "id": ""
      },
      "user": {
        "email": "michelle@keen.io",
        "id": "4fa2cccccf546ffff000006"
      },
      "user_agent": {
        "browser": "chrome",
        "browser_version": "20.0.1132.57",
        "platform": "macos"
      }
    }
  ]
}

Creates an extraction request for full-form event data with all property values.

We strongly believe you should always have full access to all of your data, and we aim to make that as simple and painless as possible.

HTTP Methods

Method Authentication Description
GET Read Key Creates an extraction request for full-form event data with all property values. JSON objects passed as query string parameters need to be URL encoded.
HEAD Read Key Returns the response header
POST Read Key Creates an extraction request for full-form event data with all property values. Each parameter and value should be placed in a JSON object within the POST body.

Required Parameters

Parameter Description
event_collection Specifies the name of the event collection to analyze.
timeframe Refines the scope of events to be included in the analysis based on when the event occurred.

Optional Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
filters Refines the scope of events to be included in the analysis based on event property values.
timezone Assigns a timezone offset to relative timeframes.
email If an email address is specified, an email will be sent to it when your extraction is ready for download. If email is not specified, your extraction will be processed synchronously and your data will be returned as JSON.
latest An integer containing the number of most recent events to extract.
property_names A URL-encoded array of strings containing properties you wish to extract. If this parameter is omitted, all properties will be returned.

Limits

Extractions are rate limited at 200/minute, but are considered separately from query rate limiting.

The maximum number of events that can be returned in a synchronous JSON response is 100,000. Requests exceeding this limit will error.

If you need to extract more than that:

  1. Break the request into several smaller requests by timeframe. For example, if a month’s worth of data is over 10M events, you could run one extraction for each week.
  2. Consider an asynchronous extraction to CSV file, which has a higher extraction limit of 10,000,000 events.

Extract to CSV file

Request

$ export EMAIL_ADDRESS=youraddress@example.com

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/extraction \
  -H "Authorization: READ_KEY" \
  -H 'Content-Type: application/json' \
  -d "{
    \"event_collection\": \"COLLECTION_NAME\",
    \"timeframe\": \"this_7_days\",
    \"email\": \"EMAIL_ADDRESS\"
  }"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  readKey: 'READ_KEY'
});

client
  .query('extraction', {
    event_collection: 'purchases',
    email: 'team@keen.io',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });

Response

{
  "result": "Processing. Check the specified email for the extraction results."
}

If the email parameter is included with a request, the extraction will be processed asynchronously and an email will be sent to the specified address when complete.

The email will include a link to a downloadable CSV file. That file will be available at that location for 30 days. Otherwise, the extraction will be processed in-line and JSON results will be returned in the request response.

Limits

The maximum number of events that can be extracted per .csv file is 10,000,000. Requests exceeding this limit will error.

Access

Projects

Overview

If you’re a B2B user, certain data models may require you to provide separate projects for each of your customers. As an alternative to creating each project by hand in the Keen IO web UI, you can utilize this feature to programmatically create Keen IO projects as your new users sign up.

Get Project

Request

$ curl https://api.keen.io/3.0/organizations/ORG_ID/projects/PROJECT_ID \
  -H "Authorization: ORGANIZATION_KEY" \
  -H "Content-Type: application/json"

import Keen from 'keen-js';

const client = new Keen();
const orgId = 'ORGANIZATION_ID';
const orgKey = 'ORGANIZATION_KEY';
const projectId = 'PROJECT_ID';

client
  .get(client.url('version', 'organizations', orgId, 'projects', projectId))
  .auth(orgKey)
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// Successsful result

200 - OK
{
    "name": "My First Project",
    "api_keys": {
        "master": "<MASTER_API_KEY>",
        "read": "<READ_API_KEY>",
        "write": "<WRITE_API_KEY>"
    },
    "users": [
        {
            "email": "<USER_EMAIL>"
        },
        {
            ...
        },
        ...
    ],
    "preferences": {
        "s3_bucket_name": "<S3_BUCKET_NAME>"
    }

}

Supported HTTP Methods

Method Authentication Description
GET Organization Key Returns information about a project.

Get Projects

Request

$ curl https://api.keen.io/3.0/organizations/ORG_ID/projects \
  -H "Authorization: ORGANIZATION_KEY" \
  -H "Content-Type: application/json"
import Keen from 'keen-js';

const client = new Keen();
const orgId = 'ORGANIZATION_ID';
const orgKey = 'ORGANIZATION_KEY';

client
  .get(client.url('version', 'organizations', orgId, 'projects'))
  .auth(orgKey)
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// Successful result
200 - OK

[
   {
        "name": "First Project",
        "api_keys": {
            "master": "<MASTER_API_KEY>",
            "read": "<READ_API_KEY>",
            "write": "<WRITE_API_KEY>"
        },
        "users": [
            {
                "email": "<USER_EMAIL>"
            },
            {
                "email": "<USER_EMAIL>"
            },
            ...
        ],
        "preferences": {
            "s3_bucket_name": "<S3_BUCKET_NAME>"
        }

    },
    {
        "name": "Second Project",
        ...
    },
    ...
]

Supported HTTP Methods

Method Authentication Description
GET Organization Key Returns information about all projects for the given organization.

Create Project

Request

$ curl https://api.keen.io/3.0/organizations/ORG_ID/projects
  -H "Authorization: ORGANIZATION_KEY" \
  -H "Content-Type: application/json"
  -d '{
        "name": "My First Project",
        "users": [
            {
                "email": "<USER_EMAIL>"
            },
            {
                ...
            },
            ...
        ],
        "preferences": {
            "s3_bucket_name": "<S3_BUCKET_NAME>"
        }
    }'
import Keen from 'keen-js';

const client = new Keen();
const orgId = 'ORGANIZATION_ID';
const orgKey = 'ORGANIZATION_KEY';

client
  .post(client.url('version', 'organizations', orgId, 'projects'))
  .auth(orgKey)
  .send({
    name: 'My First Project',
    users: [
      { email: '<USER_EMAIL>' },
      { email: '<USER_EMAIL>' },
      { email: '<USER_EMAIL>' }
      // ...
    ],
    preferences: {
      's3_bucket_name': '<S3_BUCKET_NAME>'
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
<?php

$client = KeenIOClient::factory([
    'organizationId'  => $org_id,
    'organizationKey' => $org_key
]);

$client->createProject(['name' => 'My First Project', 'users' => ['email' => '<USER_EMAIL>']]);

?>
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// Successful result
    200 - OK
{
    "name": "My First Project",
    "api_keys": {
        "master": "<MASTER_API_KEY>",
        "read": "<READ_API_KEY>",
        "write": "<WRITE_API_KEY>"
    },
    "users": [
        {
            "email": "<USER_EMAIL>"
        },
        {
           ...
        },
        ...
    ],
    "preferences": {
        "s3_bucket_name": "<S3_BUCKET_NAME>"
    }    
}

Supported HTTP Methods

Method Authentication Description
POST Organization Key Create a new project in the organization.

Required Parameters

Parameter Description
name Specifies the name of the project to be created.
users Specifies users for the project. If any users supplied do not exist, the request will fail. You will receive an error indicating which users are invalid.

Optional Parameters

Parameter Description
preferences A JSON object, currently only used for a s3 bucket name.

Update Project

Request

$ curl https://api.keen.io/3.0/organizations/ORG_ID/projects/PROJECT_ID
  -H "Authorization: ORGANIZATION_KEY" \
  -H "Content-Type: application/json"
  -d '{
        "name": "My Updated Project",
        "users": [
            {
                "email": "<USER_EMAIL>"
            },
            {
                ...
            },
            ...
        ],
        "preferences": {
            "s3_bucket_name": "<S3_BUCKET_NAME>"
        }
    }'
import Keen from 'keen-js';

const client = new Keen();
const orgId = 'ORGANIZATION_ID';
const orgKey = 'ORGANIZATION_KEY';
const projectId = 'PROJECT_ID';

client
  .post(client.url('version', 'organizations', orgId, 'projects', projectId))
  .auth(orgKey)
  .send({
    name: 'My Updated Project',
    users: [
      { email: '<USER_EMAIL>' },
      { email: '<USER_EMAIL>' }
      // ...
    ],
    preferences: {
      's3_bucket_name': '<S3_BUCKET_NAME>'
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

// Successful result
    200 - OK
{
    "name": "My Updated Project",
    "api_keys": {
        "master": "<MASTER_API_KEY>",
        "read": "<READ_API_KEY>",
        "write": "<WRITE_API_KEY>"
    },
    "users": [
        {
            "email": "<USER_EMAIL>"
        },
        {
           ...
        },
        ...
    ],
    "preferences": {
        "s3_bucket_name": "<S3_BUCKET_NAME>"
    }    
}

Supported HTTP Methods

Method Authentication Description
POST Organization Key Update an existing project in the organization.

Optional Parameters

Parameter Description
name Specifies the name of the project to update to
users Specifies all users for the project. If any users supplied do not exist, the request will fail. You will receive an error indicating which users are invalid.
preferences A JSON object, currently only used for a s3 bucket name.

Access Keys

Overview

Access Keys are used to further control access to your projects. These keys can be used to authenticate requests to collect or query data. They can also enhance those requests with various options.

Some example cases where you’d want to use a Access Key:

  1. You’re recording data on behalf of another user and want to make sure that all data recorded by that user is tagged with a specific property.
  2. You’re presenting a dashboard to a specific user and want to make sure that another user cannot see that user’s data.
  3. You want to allow certain queries to be accessible to certain users, but not others.

You can see more documenation on Access Keys below and here.

Example Access Key

{
  "name": "This is my human_readable string name!",
  "is_active": true,
  "permitted": ["writes", "queries", "saved_queries", "cached_queries", "datasets", "schema"],
  "options": {
    "writes": {
      "autofill": {
        "customer": {
          "id": "93iskds39kd93id",
          "name": "Ada Corp."
        }
      }
    },
    "queries": {
      "filters": [{
        "property_name": "customer.id",
        "operator": "eq",
        "property_value": "93iskds39kd93id"
      }]
    },
    "saved_queries": {
      "allowed": ["my_saved_query", "my_other_one"],
      "blocked": ["my_sensitive_query"],
      "filters": [{
        "property_name": "customer.id",
        "operator": "eq",
        "property_value": "93iskds39kd93id"
      }]
    },
    "cached_queries": {
      "allowed": ["my_cached_query", "my_other_one"],
      "blocked": ["my_sensitive_query"]
    },
    "datasets": {
      "operations": ["read", "list", "retrieve"],
      "allowed": {
        "my_single_index_dataset": {
          "index_by": {
            "customer.id": ["93iskds39kd93id"]
          }
        },
        "my_other_dataset_unlimited_access": {}
      },
      "blocked": ["my_sensitive_dataset"]
    }
  }
}

The following customization is available when creating specialized Access Keys. Access Key options are represented as a JSON object with the following properties. Each of the properties can be set for your use case:

Property Description
name A human readable name for the API Key. Limited to 256 characters.
is_active A boolean that indicates if the key is currently active or revoked.
permitted A list of high level actions this key can perform. You can read more on this below. Possible options: “writes”, “queries”, “saved_queries”, “cached_queries”, “datasets”, “schema”
options An object containing more details about the key’s permitted and restricted functionality.

“writes” permitted

When “writes” are permitted, the Access Key will have the ability to stream data to Keen.

Property Description
options.writes Container object for write options.
options.writes.autofill An object containing properties to be merged with properties sent during data collection.

“queries” permitted

When “queries” are permitted, the Access Key will have the ability to do ad-hoc queries.

Note: This does not include saved, cached queries, or datasets.

Property Description
options.queries Container object for query options.
options.queries.filters A list of filters that are automatically added to every query.

“saved_queries” permitted

When “saved_queries” are permitted, the Access Key will have access to run saved queries.

If you need to create a saved query, update a saved query, delete a saved query, or anything else with a saved query that requires a Master Key, this cannot be done with an Access Key.

Note: If you have a saved query that is being cached, you will need to have “cached_queries” permitted.

Property Description
options.saved_queries Container object for saved_query options.
options.saved_queries.allowed A list of saved_query names this key is allowed to access.
options.saved_queries.blocked A list of saved_query names this key cannot access.
options.saved_queries.filters A list of filters added to every saved query retrieved.

“cached_queries” permitted

When “cached_queries” are permitted, the Access Key will have access to retrieve results from cached queries.

Note: If you have a saved query that is not being cached, you will need to have “saved_queries” permitted.

Property Description
options.cached_queries Container object for cached_query options.
options.cached_queries.allowed A list of cached_queries this key is allowed to access.
options.cached_queries.blocked A list of cached_queries this key cannot access.

“datasets” permitted

When “datasets” are permitted, the Access Key will have access to getting a dataset definition, retrieving cached dataset results, and listing cached datasets definitions for a project.

Remember: If you need to create a cached datasets or delete cached datasets, this requires a Master Key and cannot be done with an Access Key.

Property Description
options.datasets Container object for Cached Dataset options.
options.datasets.operations List of possible operations - “read”, for getting definition; “list”, for getting multiple definitions; “retrieve”, for getting results], create/delete require Master Key
options.datasets.allowed An object that says which Cached Datasets this key can access, with optional limiting of “index_by”
options.datasets.blocked An object that says which Cached Datasets this cannot access

“schema” permitted

When “schema” is permitted, you can inspect schema information for a single collection or all the event collections in a given project.

Creating an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys
  -H "Authorization: MASTER_KEY" \
  -H "Content-Type: application/json"
  -d '{
        "name": "My Access Key",
        "is_active": true,
        "permitted": ["queries", "cached_queries"],
        "options": {
          "queries": {
            "filters": [
              {
                "property_name": "customer.id",
                "operator": "eq",
                "property_value": "asdf12345z"
              }
            ]
          },
          "cached_queries": {
            "allowed": ["my_cached_query"]
          }
        }
      }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .post(client.url('projectId', 'keys'))
  .auth(client.masterKey())
  .send({
    name: 'My Access Key',
    is_active: true,
    permitted: [ 'queries', 'cached_queries' ],
    options: {
      queries: {
        filters: [
          {
            property_name: 'customer.id',
            operator: 'eq',
            property_value: 'f234124dsfb'
          }
        ]
      },
      cached_queries: {
        allowed: [ 'my-cached-query' ]
      }
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

201 - Created

{
  "key": "SDKFJSDKFJSDKFJSDKFJDSK",
  "name": "My Access Key",
  "is_active": true,
  "permitted": ["queries", "cached_queries"],
  "options": {
    "queries": {
      "filters": [
        {
          "property_name": "customer.id",
          "operator": "eq",
          "property_value": "asdf12345z"
        }
      ]
    },
    "cached_queries": {
      "allowed": ["my_cached_query"]
    }
  }
}

Supported HTTP Methods

Method Authentication Description
POST Master Key Create a new API Key for the project.

List all Access Keys

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys
  -H "Authorization: MASTER_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .get(client.url('projectId', 'keys'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

200 - OK

{
  "keys": [
    {
      "key": "SDKFJSDKFJSDKFJSDKFJDSK",
      "name": "My Access Key",
      "is_active": true,
      "permitted": ["queries", "cached_queries"],
      "options": {
        "queries": {
          "filters": [
            {
              "property_name": "customer.id",
              "operator": "eq",
              "property_value": "asdf12345z"
            }
          ]
        },
        "cached_queries": {
          "allowed": ["my_cached_query"]
        }
      }  
    }
  ]
}

Supported HTTP Methods

Method Authentication Description
GET Master Key Retrieves a list of Access Keys.

Get an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys/CUSTOM_KEY
  -H "Authorization: MASTER_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const keyName = 'my-custom-key-name';

client
  .get(client.url('projectId', 'keys', keyName))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

200 - OK

{
  "key": "SDKFJSDKFJSDKFJSDKFJDSK",
  "name": "My Access Key",
  "is_active": true,
  "permitted": ["queries", "cached_queries"],
  "options": {
    "queries": {
      "filters": [
        {
          "property_name": "customer.id",
          "operator": "eq",
          "property_value": "asdf12345z"
        }
      ]
    },
    "cached_queries": {
      "allowed": ["my_cached_query"]
    }
  }  
}

Supported HTTP Methods

Method Authentication Description
GET Master Key Retrieves an Access Key definition.

Updating an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys/CUSTOM_KEY
  -H "Authorization: MASTER_KEY" \
  -H "Content-Type: application/json"
  -d '{
        "name": "My Updated Access Key",
        "is_active": true,
        "permitted": ["queries", "cached_queries"],
        "options": {
          "queries": {
            "filters": [
              {
                "property_name": "customer.id",
                "operator": "eq",
                "property_value": "asdf12345z"
              }
            ]
          },
          "cached_queries": {
            "allowed": ["my_cached_query", "another_cached_query"]
          }
        }
      }'
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const keyName = 'my-custom-key-name';

client
  .post(client.url('projectId', 'keys', keyName))
  .auth(client.masterKey())
  .send({
    name: 'My Updated Access Key',
    is_active: true,
    permitted: [ 'queries', 'cached_queries' ],
    options: {
      queries: {
        filters: [
          {
            property_name: 'customer.id',
            operator: 'eq',
            property_value: 'f234124dsfb'
          }
        ]
      },
      cached_queries: {
        allowed: [
          'my-cached-query',
          'another-cached-query'
        ]
      }
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

200 - OK

{
  "name": "My Updated Access Key",
  "is_active": true,
  "permitted": ["queries", "cached_queries"],
  "options": {
    "queries": {
      "filters": [
        {
          "property_name": "customer.id",
          "operator": "eq",
          "property_value": "asdf12345z"
        }
      ]
    },
    "cached_queries": {
      "allowed": ["my_cached_query", "another_cached_query"]
    }
  }
}

Supported HTTP Methods

Method Authentication Description
POST Master Key Updates an Access Key

Revoking an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys/CUSTOM_KEY/revoke
  -H "Authorization: MASTER_KEY" \
  -X POST
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const keyName = 'my-custom-key-name';

client
  .post(client.url('projectId', 'keys', keyName, 'revoke'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

204 - No Content

Supported HTTP Methods

Method Authentication Description
POST Master Key Revokes an Access Key

Un-revoking an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys/CUSTOM_KEY/unrevoke
  -H "Authorization: MASTER_KEY" \
  -X POST
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

const keyName = 'my-custom-key-name';

client
  .post(client.url('projectId', 'keys', keyName, 'unrevoke'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Currently not supported by this SDK.
# Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

204 - No Content

Supported HTTP Methods

Method Authentication Description
POST Master Key Unrevokes an Access Key

Maintenance

Discovery

Request

$ curl "https://api.keen.io/3.0?api_key=MASTER_KEY"
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .post(client.url('version'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });

Response

{
  "projects_resource_url": "/3.0/projects"
}

Returns all available child resources.

HTTP Methods

Method Authentication Response
GET Master Key All available resources
HEAD Master Key Response header

Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Delete

This section describes some of the maintenance functions of the Keen IO API. All maintenance API calls require the use of the Master API Key.

Tips and best practices:

  • Deletes are irreversible and it’s easy to make mistakes, so proceed with caution.
  • Preview the data that will be deleted by running a count and an extraction with the desired filters, timeframe and timezone parameters. The extraction also gives you an extra backup of your data in case you make a mistake.
  • Be sure to pass filters as query string parameters, not as a part of a content body where they will be ignored.
  • Deleting events should be done sparingly, not as a matter of routine. Running large number of deletes, or using deletes as a way to update old events is an anti-pattern. Unlike writes and queries, deletes are not a highly performant operation on the platform.
  • DELETE requests are rate limited at 10/minute, regardless of which resource is being deleted. Learn more about limits.
  • The max number of events you can delete when specifying filters is 100,000. If attempting to delete more than 100,000 events, subdivide by timeframe.

Delete a Collection

Resource

https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=MASTER_KEY

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME \
    -H "Authorization: MASTER_KEY" \
    -H 'Content-Type: application/json' \
    -X DELETE
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .del(client.url('events', 'my-event-stream'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
Keen.delete(:signups)  # => true
keen.delete_events("signups")
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Supported by this SDK.

Response

// Successful result
204 - No Content

After testing, you may have some event collections you want to delete. You can do this using the projects page, or our API.

Supported HTTP Methods

Method Authentication Description
DELETE Master Key Delete a single event collection

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Delete Events

Resource

https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME?api_key=MASTER_KEY&filters=YOUR_FILTERS_HERE&timeframe=this_7_days

Request

# This example deletes all events that meet the criteria of a supplied set of filters:

$ curl "https://api.keen.io/3.0/projects/${PROJECT_ID}/events/${COLLECTION_NAME}?filters=%5B%7B%22property_name%22%3A%22${PROPERTY_NAME}%22%2C%22operator%22%3A%22${PROPERTY_OPERATOR}%22%2C%22property_value%22%3A%22${PROPERTY_VALUE}%22%7D%5D&timeframe=this_7_days" \
  -H "Authorization: ${MASTER_KEY}" \
  -X DELETE
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

/*
  Filters and timeframe must be passed as encoded query string parameters. This example constructs a complete URL to ensure the request is executed properly.
*/
const url = client.url('events', 'my-event-stream', {
  api_key: client.masterKey(),
  filters: encodeURIComponent(JSON.stringify([
    {
      property_name: 'user.id',
      operator: 'eq',
      property_value: 'f1243353243fdb'
    }
  ])),
  timeframe: encodeURIComponent(JSON.stringify({
    start: '2015-05-15T19:00:00.000Z',
    end: '2015-06-07T19:00:00.000Z'
  })),
  timezone: 'US/Pacific'
});

client
  .del(url)
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# Or just delete an event corresponding to a particular user
Keen.delete(:signups, :timeframe => "this_7_days", :filters => [{
  property_name: 'username', operator: 'eq', property_value: "Bob"
}])  # => true
keen.delete_events("signups", timeframe="this_7_days", filters=[
  {
    "property_name": 'username',
    "operator": 'eq',
    "property_value": 'Bob'
  }
])
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Supported by this SDK.

Response

// Successful result
204 - No Content

The DELETE method can be used to delete specific events that meet your filter criteria. Please note this functionality should be used in one-off cases rather than in regular use in your data model.

Supported HTTP Methods

Method Authentication Description
DELETE Master Key Deletes events meeting criteria of supplied filters, timeframe and timezone parameters

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header
filters Optional filters to use when selecting events for deletion
timeframe Optional timeframes to use when selecting events for deletion
timezone Optional timezone to use when specifying a timeframe

Delete a Property

DELETE Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/events/COLLECTION_NAME/properties/PROPERTY_NAME \
    -H "Authorization: MASTER_KEY" \
    -H "Content-Type: application/json" \
    -X DELETE
import Keen from 'keen-js';

const client = new Keen({
  projectId: 'PROJECT_ID',
  masterKey: 'MASTER_KEY'
});

client
  .del(client.url('events', 'my-event-stream', properties, 'ip_address'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Currently not supported by this SDK.
// Supported by this SDK.

Response

204 - No Content

Supported HTTP Methods

Method Authentication Description
DELETE Master Key Delete a property from an event collection.

Optional Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header

Delete Projects

Projects can be deleted from the projects panel. Deleting a project puts it in an inactive state and removes it from the admin interface.

Wardrobe

Overview

The Wardrobe resource returns textiles meeting specified criteria.

Wardrobe Resource

Resource

https://api.keen.io/3.0/projects/523b527836bf5a3216000006/events/wardrobe

Request

$ curl https://api.keen.io/3.0/projects/523b527836bf5a3216000006/events/wardrobe \
    -H "Authorization: SPECIAL_KEY_SEE_BELOW" \
    -H 'Content-Type: application/json' \
    -d '{
      "name": "Lara Croft",
      "email": "lara@lcroft.com",
      "address_line_1": "2624 15th Ave",
      "address_line_2": "Apt 1",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94127",
      "country": "USA",
      "garment_gender": "Womens",
      "size": "L"
    }'

Response

// Successful result
204 - {"created":true}

Use this resource to request a garment of a given size to a given address. This is really just the Event Collection Resource, except you must use a specific project ID, Write Key, and collection name. See below.

Here’s an example of what the event collection request would look like using the REST API.

Request a garment.

Supported HTTP Methods

Method Authentication Description
POST Special Key Request a garment

Required Request Parameters

Parameter Description
name A name for the garmet recipient
email A valid email address
address_line_1 First line of your shipping address
address_line_2 Second line if needed
city Name of your home city
state State or Province
country Country
zip Zip or postal code
garment_gender Women's or Unisex
size S, M, L, or XL