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 HTTP API requests and one for Kafka API. All are 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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.

HTTP 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.

Kafka Producer

To authenticate a Kafka Producer to the Keen Kafka Inbound Cluster use the SASL_SSL security protocol and the PLAIN SASL mechanism. Use your PROJECT_ID as the username and the project WRITE_KEY as password.

Request

$ kafka-console-producer.sh \
    --bootstrap-server b1.kafka-in.keen.io:9092,b2.kafka-in.keen.io:9092,b3.kafka-in.keen.io:9092 \
    --topic "Target-collection-name" \
    --producer-property security.protocol=SASL_SSL \ 
    --producer-property sasl.mechanism=PLAIN \
    --producer-property sasl.jaas.config='org.apache.kafka.common.security.plain.PlainLoginModule required username="PROJECT_ID" password="WRITE_KEY";'
from kafka import KafkaProducer

KafkaProducer(
    bootstrap_servers=["b1.kafka-in.keen.io:9092", "b2.kafka-in.keen.io:9092", "b3.kafka-in.keen.io:9092"],
    security_protocol="SASL_SSL",
    sasl_mechanism="PLAIN",
    value_serializer=lambda v: v.encode("utf-8"),
    sasl_plain_username="PROJECT_ID",
    sasl_plain_password="WRITE_KEY"
)
import org.apache.kafka.clients.producer.KafkaProducer;
...

Properties props = new Properties();
props.put(BOOTSTRAP_SERVERS_CONFIG, "b1.kafka-in.keen.io:9092,b2.kafka-in.keen.io:9092,b3.kafka-in.keen.io:9092");
props.put(SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
props.put(SASL_MECHANISM, "PLAIN");
props.put(SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"PROJECT_ID\" password=\"WRITE_KEY\";");
props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
new KafkaProducer<>(props);

Kafka Consumer

To authenticate a Kafka Consumer to the Keen Kafka Outbound Cluster use the SASL_SSL security protocol and the PLAIN SASL mechanism. Use your PROJECT_ID as the username and the project READ_KEY as password.

Request

$ kafka-console-consumer.sh \
    --bootstrap-server b1.kafka-out.keen.io:9092,b2.kafka-out.keen.io:9092,b3.kafka-out.keen.io:9092 \
    --topic "Target-collection-name" \
    --consumer-property security.protocol=SASL_SSL \
    --consumer-property sasl.mechanism=PLAIN \
    --consumer-property sasl.jaas.config='org.apache.kafka.common.security.plain.PlainLoginModule required username="PROJECT_ID" password="READ_KEY";'
from kafka import KafkaConsumer

KafkaConsumer(
    "Target-collection-name",
    bootstrap_servers=["b1.kafka-out.keen.io:9092", "b2.kafka-out.keen.io:9092", "b3.kafka-out.keen.io:9092"],
    security_protocol="SASL_SSL",
    sasl_mechanism="PLAIN",
    sasl_plain_username="PROJECT_ID",
    sasl_plain_password="READ_KEY",
    group_id="YOUR_GROUP_ID"
)
import org.apache.kafka.clients.consumer.KafkaConsumer;
...

Properties props = new Properties();
props.put(BOOTSTRAP_SERVERS_CONFIG, "b1.kafka-out.keen.io:9092,b2.kafka-out.keen.io:9092,b3.kafka-out.keen.io:9092");
props.put(SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
props.put(SASL_MECHANISM, "PLAIN");
props.put(SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"PROJECT_ID\" password=\"READ_KEY\";");
props.put(KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(GROUP_ID_CONFIG, "YOUR_GROUP_ID");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

API Keys

Example

import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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. 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 Stream section of the docs.

Read Key

import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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. 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 Compute section of the docs.

Organization Key

The Organization Key is an API Key for programmatically managing your Keen 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.

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 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.

Rate Limiting

Request Type Request Limits
Recording Events No Limit!
Ad-Hoc Queries 200/minute
Extractions 200/minute
Updates 10/minute
Deletes 10/minute
Delete a Collection 100/minute

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 lookups 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 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.

Response Size Limit

The response for the most of the analysis types is a small json object containing the key “result”, and the computed value. Some features can greatly explode the size of the result. Mostly, these fall into following categories:

  • group_by’s on a property (or set of properties) that has many distinct values,
  • queries using the select_unique analysis type that have a high cardinality target_property,
  • queries using interval with many sub-timeframes,
  • combination of the above.

Queries with the response size larger than 150 MB will error with a meaningful message.

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. 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.

Timeframe Limits

Timeframe limits applies only to the Free plan. You are not allowed to query events older than 3 months on the Free plan. Events older than 3 months won’t be included in the query results.

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 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 KeenAnalysis from 'keen-analysis';

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

client
  .get(client.url('base'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('base'),
    api_key: client.masterKey(),
  })
  .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.

HTTP 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 KeenTracking from 'keen-tracking';

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

client
  .recordEvent('purchases', {
    item: 'Avocado',
    number_of_items: 10,
    user: {
      name: 'John Smith'
    }
  })
  .then((response) => {
    // handle successful responses
  })
  .catch(error => {
    // handle errors
  });
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

HTTP 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 KeenTracking from 'keen-tracking';

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

const multipleEvents = {
  signups: [
    { username: 'rob' },
    { username: 'mary' }
  ],
  purchases: [
    { item: 'avocado', price: 10 },
    { item: 'orange', price: 20 }
  ],
};

client
  .recordEvents(multipleEvents)
  .then((response) => {
    // handle successful responses
  })
  .catch(error => {
    // handle errors
  });
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.

Kafka Event Streaming

Keen now offers streaming using Apache Kafka as one of the interfaces to stream and backup events making integration with external systems easy.

Producing events

Request

$ kafka-console-producer.sh \
    --bootstrap-server b1.kafka-in.keen.io:9092,b2.kafka-in.keen.io:9092,b3.kafka-in.keen.io:9092 \
    --topic "collection-name" \
    --producer-property security.protocol=SASL_SSL \ 
    --producer-property sasl.mechanism=PLAIN \
    --producer-property sasl.jaas.config='org.apache.kafka.common.security.plain.PlainLoginModule required username="PROJECT_ID" password="WRITE_KEY";'
>{"key": 123}
>
from kafka import KafkaProducer

kafka_producer = KafkaProducer(
    bootstrap_servers=["b1.kafka-in.keen.io:9092", "b2.kafka-in.keen.io:9092", "b3.kafka-in.keen.io:9092"],
    security_protocol="SASL_SSL",
    sasl_mechanism="PLAIN",
    value_serializer=lambda v: v.encode("utf-8"),
    sasl_plain_username="PROJECT_ID",
    sasl_plain_password="WRITE_KEY"
)
kafka_producer.send(topic="Target-collection-name", value="{\"key\":123}")
import org.apache.kafka.clients.producer.KafkaProducer;
...

Properties props = new Properties();
props.put(BOOTSTRAP_SERVERS_CONFIG, "b1.kafka-in.keen.io:9092,b2.kafka-in.keen.io:9092,b3.kafka-in.keen.io:9092");
props.put(SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
props.put(SASL_MECHANISM, "PLAIN");
props.put(SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"PROJECT_ID\" password=\"WRITE_KEY\";");
props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

KafkaProducer kafkaProducer = new KafkaProducer<>(props);
kafkaProducer.send(new ProducerRecord<>("Target-collection-name", "{\"key\":123}"));

A Kafka producer authenticated with a Write Key may stream events to a topic. There is no need to create a topic in advance, the auto.create.topics.enable=true. The topic name must be unique within a project. The topic name is used as the collection name for the events streamed. Events streamed to the Keen Kafka Inbound Cluster are processed the same way as in the HTTP API.

Consuming events

Request

kafka-console-consumer.sh \
    --bootstrap-server b1.kafka-out.keen.io:9092,b2.kafka-out.keen.io:9092,b3.kafka-out.keen.io:9092 \
    --topic "Target-collection-name" \
    --consumer-property security.protocol=SASL_SSL \
    --consumer-property sasl.mechanism=PLAIN \
    --consumer-property sasl.jaas.config='org.apache.kafka.common.security.plain.PlainLoginModule required username="PROJECT_ID" password="READ_KEY";'
    --from-beginning
from kafka import KafkaConsumer

KafkaConsumer(
            "Target-collection-name",
            bootstrap_servers=["b1.kafka-out.keen.io:9092", "b2.kafka-out.keen.io:9092", "b3.kafka-out.keen.io:9092"],
            security_protocol="SASL_SSL",
            sasl_mechanism="PLAIN",
            sasl_plain_username="PROJECT_ID",
            sasl_plain_password="READ_KEY",
            group_id="YOUR_GROUP_ID"
        )

polled_messages = kafka_consumer.poll()
for partition in polled_messages.values():
    for consumer_record in partition:
        print(consumer_record.value.decode("utf-8"))
import org.apache.kafka.clients.consumer.KafkaConsumer;
...

        Properties props = new Properties();
        props.put(BOOTSTRAP_SERVERS_CONFIG, "b1.kafka-out.keen.io:9092,b2.kafka-out.keen.io:9092,b3.kafka-out.keen.io:9092");
        props.put(SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
        props.put(SASL_MECHANISM, "PLAIN");
        props.put(SASL_JAAS_CONFIG, "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"PROJECT_ID\" password=\"READ_KEY\";");
        props.put(KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(GROUP_ID_CONFIG, "YOUR_GROUP_ID");

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

        consumer.subscribe(List.of("Target-collection-name"));
        consumer.poll(Duration.of(30, ChronoUnit.SECONDS))
            .forEach(record -> System.out.println(record.key() + " " +record.value()));

You can consume event streams using a Kafka Consumer authenticated to a topic with a Read Key. There is no need to create a topic in advance, the auto.create.topics.enable=true. To consume from some specific event collection use the collection name as the Kafka Consumer’s topic (if you are also using Kafka for streaming events this will be the same topic name as used by the Kafka Producer). Events sent using HTTP API and a Kafka Producer will both be streamed to the Outbound Kafka Cluster. You can send some of your events using HTTP API and some using a Kafka Producer and then consume all of your events using a Kafka Consumer. All events available for consumption in the Outbound Kafka Cluster are enriched as described in Data Enrichment.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('events', 'COLLECTION_NAME'),
    api_key: client.readKey(),
  })
  .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

{
  "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"
  }
}

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 KeenAnalysis from 'keen-analysis';

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

client
  .get(client.url('events'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('events'),
    api_key: client.readKey(),
  })
  .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 the event collections in a given project, along with properties (and their types), and links to sub-resources. Up to 5000 event collections is returned.

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
include_schema Optional boolean flag indicating if properties should be returned in the response. When not provided it defaults to true. Allowed values:true, false.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('events', 'COLLECTION_NAME', 'properties', 'keen.id'),
    api_key: client.readKey(),
  })
  .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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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 three 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": [ {} ],
    "uniqueness_token": "7ae05b64-eab5-4dda-a0e9-ea897fcc9c4a"
  },
  "user": {
    "name": "Smacko",
    "age": 21
  }
}
Property Description
keen.uniqueness_token Your unique event ID, used for duplicate prevention. You can read more about event IDs in our data modelling guide.
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.

Duplicate prevention

When sending events, it is possible to receive an error even though your event was actually saved successfully. For example, if you send an event, and the request times out, you can’t be sure whether Keen saved your event, or not. If you simply retry the request, you might end up with a duplicate event.

Uniqueness Token

Uniqueness Token example usage

{
  "keen": {
    "uniqueness_token": "7ae05b64-eab5-4dda-a0e9-ea897fcc9c4a"
  },
  "customer": {
    "id": "0808130sdfsf801",
    "email": "email@address.com"
  }
}

In order to prevent duplicates, Keen allows you to supply a keen.uniqueness_token, an optional property in the Keen object, in your event data. This property can be set to any UTF-8 encoded String and must not be longer than 64 characters. If a subsequent event with the same keen.uniqueness_token is sent to the same collection within 31 days, it will be dropped silently. We recommend token values to be cryptographic hashes of a list of properties uniquely identifying the event. For example, in the case of tracking video views, an event representing the “video has been played” could have the following uniqueness token: uniqueness_token = sha256(video_identifier + timestamp + user_session_identifier).

Use keen.uniqueness_token in conjunction with a retry mechanism to achieve ‘Exactly Once’ event delivery semantics. Read more about the uniqueness token in our Data Modeling Guide.

Data Enrichment

Keen 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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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 properties:
- A mandatory key of “ip” with a value of the name of the property containing the IP address to parse.
- An optional key “remove_ip_property” with a value true, to remove the “ip” property from the event, after the IP to GEO enrichment.

{ "ip": "property.containing.ip_address" } or
{ "ip": "property.containing.ip_address", "remove_ip_property": true }
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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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",
      "type" : "mobile phone"
    },
    "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”
device.type A string containing the device type ie: “mobile phone”. When user agent is invalid that property doesn’t exists.
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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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 KeenTracking from 'keen-tracking';

const client = new KeenTracking({
  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, sum and standard deviation 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 KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'purchases',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

Queries are rate limited at 200/minute.

Count Unique

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count_unique',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'minimum',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

Queries are rate limited at 200/minute.

Maximum

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'maximum',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

Queries are rate limited at 200/minute.

Sum

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'sum',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

Queries are rate limited at 200/minute.

Average

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'average',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

Queries are rate limited at 200/minute.

Median

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'median',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Approximation

The API will switch from an exact result to an approximated result above 1,000,000 values. If you want to geek out about statistics, give us a shout.

Limits

Queries are rate limited at 200/minute.

Percentile

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'percentile',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

Approximation

The API will switch from an exact result to an approximated result above 1,000,000 values. If you want to geek out about statistics, give us a shout.

Limits

Queries are rate limited at 200/minute.

Select Unique

Request

# 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'select_unique',
    eventCollection: 'purchases',
    targetProperty: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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.

Standard Deviation

Request

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

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

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

client
  .query('standard_deviation', {
    event_collection: 'purchases',
    target_property: 'price',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'standard_deviation',
    eventCollection: 'purchases',
    targetProperty: 'price',
    timeframe: 'this_7_days',
  })
  .then(res => {
    // Handle results
  })
  .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.
// 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 standardDeviation = queryClient.standardDeviation("<event_collection>", "<target_property>", new RelativeTimeframe("this_week"));
// Currently not supported by this SDK.

Response

{
  "result": 6.75
}

Calculate the standard deviation 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 standard deviation 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 standard deviation 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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'multi_analysis',
    eventCollection: 'pageviews',
    analyses: {
      'unique users': {
        analysisType: 'count_unique',
        targetProperty: 'user.id',
      },
      'total visits': {
        analysisType: '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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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', // = visitor.guid from the step above
        timeframe: 'this_7_days'
      },
      {
        event_collection: 'referred user',
        actor_property: 'user.guid',
        timeframe: 'this_7_days'
      }
    ]
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'funnel',
    steps: [
      {
        eventCollection: 'signed up',
        actorProperty: 'visitor.guid',
        timeframe: 'this_7_days',
      },
      {
        eventCollection: 'completed profile',
        actorProperty: 'user.guid', // = visitor.guid from the step above
        timeframe: 'this_7_days',
      },
      {
        eventCollection: 'referred user',
        actorProperty: '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"
];
$steps = [
    'steps' => array($step1, $step2, $step3)
];
$client->funnel($steps);

?>
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.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'extraction',
    eventCollection: 'purchases',
    filters: [
      {
        propertyName: 'item.price',
        operator: 'gt',
        propertyValue: 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
  }
])
keen.count("purchases",
  filters=[{
    "property_name": "item.price",
    "operator": "gt",
    "property_value": 10
  }],
  timeframe="this_14_days")
<?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 (except Or Filters) 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
or “Or” - This is a special filter that matches an event if any of its component filters match. See Or Filters.
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, or list property value contains given element.
not_contains Whether or not the string property value does not contain the given sequence of characters, or list property value does not contain given element.
within Used to select events within a certain radius of the provided geo coordinate (for geo analysis only).
regex Matching property name with regular expression in property value. Example: [A-Z][a-z]+ will match Test, Foo, Bar, but will not match: A, AAA, aaaa.

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.

Note: We are using google/re2 syntax for regex expressions. To get more information about re2 syntax go to documentation page.

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

Or 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\": [
        {
          \"operator\" : \"or\",
          \"operands\" : [
            {
              \"property_name\" : \"price\",
              \"operator\" : \"gte\",
              \"property_value\" : 9.99
            },
            {
              \"property_name\" : \"customer.tier\",
              \"operator\" : \"eq\",
              \"property_value\" : \"premium\"
            }
          ]
        }
      ]
    }"
import KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'purchases',
    filters: [
      {
        operator: 'or',
        operands: [
          {
            property_name: 'user.full_name', // uuids, aliases etc
            operator: 'eq',
            property_value: 'John Smith'
          },
          {
            property_name: 'user.full_name',
            operator: 'eq',
            property_value: 'Eva Smith'
          }
        ],
      }
    ],
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'purchases',
    filters: [
      {
        operator: 'or',
        operands: [
          {
            propertyName: 'user.full_name', // uuids, aliases etc
            operator: 'eq',
            propertyValue: 'John Smith',
          },
          {
            propertyName: 'user.full_name',
            operator: 'eq',
            propertyValue: 'Eva Smith',
          }
        ],
      },
    ],
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });

Or filters allow you to filter on events that match one or more of an array of filters, but not necessarily all of them. For example you could query the count of purchase events that either were over some price threshold or were made by a premium subscriber. Simply pass the component filters in the operands parameter. You may use any of the filter operators inside an or filter.


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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'purchases',
    filters: [
      {
        propertyName: 'keen.location.coordinates',
        operator: 'within',
        propertyValue: {
          coordinates: [ -122.42005, 37.77479 ],
          maxDistanceMiles: 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'logins',
    groupBy: '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. There are two types of group_by arguments:

  • string property_name (co-occurrence of a specified property),
  • object (ranges 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?
  • How many items were purchased within the given price ranges?

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 argument
  2. The result of the analysis


Group By Buckets

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": "temperature_measurements",
      "timeframe": "this_14_days",
      "group_by": [{
        "buckets": {
          "size": 10,
          "offset": 5
        },
        "property_name": "temperature.celsius",
        "alias": "temperature"
      }]
    }'
// 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.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

{
  "result": [
    {
      "temperature": "[-5, 5)",
      "result": 2
    },
    {
      "temperature": "[5, 15)",
      "result": 12
    },
    {
      "temperature": "[15, 25)",
      "result": 5
    },
    {
      "temperature": "[35, 45)",
      "result": 1
    }
  ]
}

The feature is currently only available via the Keen API. It’s not yet available in the Explorer or the Saved Queries view.

Group by buckets allow you to divide a range into a series of intervals, or buckets (e.g. count of transactions grouped in $10 increments). You can categorize numeric properties into buckets by providing the size and the offset from the start. A count query with group_by buckets can be visualized as a Histogram.

The formula for calculating a bucket is: X = Math.floor((property_value - offset) / size) * size + offset, then the bucket is defined as: [X, X + size). The left edge is inclusive, the right edge is exclusive.

Required Parameters

Parameter Description
buckets.size The bucket width. Must be a positive number.
property_name The property name which values are assigned to buckets.

Optional Parameters

Parameter Description
buckets.offset The point where buckets calculation starts. If not provided 0 is used.
alias The string that is returned in the query response to identify bucket values. If not provided buckets_ + N is used, where N is based on the clause position in the group_by array.

Group By Ranges

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": [{
        "ranges": [
          {"to": 1, "to_closed": true},
          {"from": 2, "to": 20, "from_closed": false},
          {"from": 100, "key": "very_expensive"}
        ],
        "property_name": "item.price",
        "alias": "price_ranges"
      }]
    }'
// 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.
// Currently not supported by this SDK.
// Currently not supported by this SDK.

Response

{
  "result": [
    {
      "price_ranges": "(-Infinity, 1]",
      "result": 12
    },
    {
      "price_ranges": "(2, 20)",
      "result": 5
    },
    {
      "price_ranges": "very_expensive",
      "result": 1
    }
  ]
}

The feature is currently only available via the Keen API. It’s not yet available in the Explorer or the Saved Queries view.

Group by ranges allow you to categorize numeric and string properties into explicit ranges provided in the request. All ranges present in the request will be returned in the response regardless to the chosen analysis result.

Required Parameters

Parameter Description
property_name The property name which values are assigned to buckets.

Optional Parameters

Parameter Description
ranges.from The start of a range. Must be lower or equal than to. If not provided defaults to -Infinity.
ranges.to The end of a range. Must be greater or equal to from. If not provided defaults to Infinity.
ranges.from_closed If start of a range is inclusive. If not provided defaults to true.
ranges.to_closed If end of a range is inclusive. If not provided defaults to false.
ranges.key The string to represent the range in the response. If not provided defaults to [from, to).
alias The string that is returned in the query response to identify bucket values. If not provided defaults to ranges_ + N, where N is the position in the group_by array.
allow_range_overlap If any two ranges are overlapping you need to confirm that you are aware of that situation by setting the property to true. If not provided defaults to false, which means that a query with any overlapping ranges will error.

Multiple Group By Clauses

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'purchases',
    groupBy: [ '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 group by clause, by providing an array of either string property names or objects to define ranges, or the mix of both.

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


Grouping By 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'logins',
    groupBy: '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 provided criteria and 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.

By default, every interval has the same set of group_by keys. To get only the non-zero values, set the zero_fill parameter to false (default: true). This might be useful especially when you want to reduce the size of a very large response.

Order 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",
      "order_by": "result"
    }'
import KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'logins',
    group_by: 'user.email',
    order_by: 'result',
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'logins',
    groupBy: 'user.email',
    orderBy: 'result',
    timeframe: 'this_14_days',
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
# not yet supported, let us know you're waiting!
keen.count("logins", group_by="user.email", timeframe="this_14_days", order_by={"property_name": "result"})
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!

Response

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

The order_by parameter orders results returned by a group_by query. Ordering can be performed on any property that is part of the group_by, or by the result value of the specified analysis. Additionally, ordering can be ASC for ascending (default) or DESC for descending, and can be limited to a subset of groups.

This type of analysis can help answer questions such as:

  • What are the top 10 most popular article titles from last week?
  • What is the rank of all states based on sum purchases during Black Friday?
  • Who are the top 100 influencing users based on invites sent to friends?

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

Adding the order_by parameter allows you to specify the order and number of results returned in your request but does not change the data structure of your response.


Specifying descending or ascending direction for ordering

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",
      "order_by": {"property_name": "result", "direction": "DESC"}
    }'
import KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'logins',
    group_by: 'user.email',
    order_by: {'property_name': 'result', 'direction': 'DESC'},
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'logins',
    groupBy: 'user.email',
    orderBy: {'property_name': 'result', 'direction': 'DESC'},
    timeframe: 'this_14_days',
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
# not yet supported, let us know you're waiting!
keen.count("logins", group_by="user.email", timeframe="this_14_days",
           order_by={"property_name": "result", "direction": keen.direction.ASCENDING})
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!

Response

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

You can specify a direction parameter to return results in for order_by. If omitted, the default ordering is ascending (“ASC”).


Ordering 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" ],
      "order_by": [{"property_name": "result", "direction": "DESC"}, {"property_name": "item.type", "direction": "ASC"}]
    }'
import KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'purchases',
    group_by: [ 'item.name', 'item.type' ],
    order_by: [{'property_name': 'result', 'direction': 'DESC'}, {'property_name': 'item.name', 'direction': 'ASC'}]
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'purchases',
    groupBy: [ 'item.name', 'item.type' ],
    orderBy: [{'property_name': 'result', 'direction': 'DESC'}, {'property_name': 'item.name', 'direction': 'ASC'}]
    timeframe: 'this_14_days',
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
# not yet supported, let us know you're waiting!
keen.count("logins", group_by="user.email", timeframe="this_14_days", order_by=[{"property_name": "result", "direction": keen.direction.DESCENDING}, {"property_name": "item.name", "direction": keen.direction.ASCENDING}])
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!

Response

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

You can specify more than one property to order_by, with a URL-encoded JSON array of objects. One of the multiple property names specified can be result.

The response structure for this request orders the results by the first property name and direction specified, and in the case of a tie sorts by the second name and direction specified, then so on and so forth.


Limiting the number of groups returned

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",
      "order_by": "result",
      "limit": 1
    }'
import KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'logins',
    group_by: 'user.email',
    order_by: 'result',
    limit: '1',
    timeframe: 'this_14_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: 'logins',
    groupBy: 'user.email',
    orderBy: 'result',
    limit: '1',
    timeframe: 'this_14_days',
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
# not yet supported, let us know you're waiting!
keen.count("logins", group_by="user.email", timeframe="this_14_days", limit=10, order_by={"property_name": "result"})
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!
// not yet supported, let us know you're waiting!

Response

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

You can set a limit parameter, which will limit the number of results you get based on the direction you set. The limit parameter will apply to each interval. We will still compute the full results, but only return a certain amount to you.

For example, if you set the limit parameter to 10 you will get the top 10 results in your timeframe. If you set a limit parameter and have a daily interval for your timeframe, you will receive the top 10 results for each day in your timeframe.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: '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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: '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 KeenAnalysis from 'keen-analysis';

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

client
  .query('count', {
    event_collection: 'logins',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: '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_minutes”
previous_hour convenience for “previous_1_hours”
yesterday convenience for “previous_1_days”
previous_day convenience for “previous_1_days”
previous_week convenience for “previous_1_weeks”
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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'count',
    eventCollection: '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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

Keen’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.

Execution Metadata

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/count?include_metadata=true \
    -H "Authorization: READ_KEY" \
    -H 'Content-Type: application/json' \
    -d "{
      \"event_collection\": \"COLLECTION_NAME\",
      \"timeframe\": {
        \"start\": \"2018-10-01T00:00:00.000Z\",
        \"end\": \"2018-11-01T00:00:00.000Z\"
      }
    }"

Response

{
  "result": 5,
  "execution_metadata": {
    "total_processing_time": 0.09057211875915527,
    "events_scanned": 1000,
    "properties_per_event": 4,
    "total_properties_scanned": 4000
  }
}

Keen’s pricing is mostly based on two factors: how much new data you collect, and how much you crunch. You can easily calculate additional information concerning costs generated by a specific query, when you pass include_metadata=true as an additional query parameter. The query result will be enriched with execution_metadata object, composed of numbers that indicate the count of scanned events, properties, and total time required to process your request.


Metadata properties

Property Description
total_processing_time Gives info on how long it takes to process the query in seconds.
events_scanned The number of events that exist within the timeframe you provided.
properties_per_event The number of properties per event required to calculate the query.
total_properties_scanned The total number of properties scanned during the query execution.

Saved Queries

Saved queries allow you to easily access your favourite 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 '{
          "refresh_rate": 0,
          "query": {
            "analysis_type": "sum",
            "target_property": "price",
            "event_collection": "purchases",
            "filters": [
              {
                "property_name": "price",
                "operator": "gte",
                "property_value": 1.00
              }
            ],
            "group_by": [
              "platform"
            ],
            "order_by": [
              {
                "property_name": "result",
                "direction": "DESC"
              }
            ],
            "timeframe": "this_2_weeks",
            "limit": 20
          }
        }'
// Supported by this SDK.
import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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: 'sum',
      target_property: 'price',
      event_collection: 'purchases',
      timeframe: 'this_2_weeks',
      filters: [
        {
          property_name: 'price',
          operator: 'gte',
          property_value: 1.00
        }
      ],
      group_by: [
        'platform'
      ],
      order_by: [
         {
           property_name: 'result',
           direction: 'DESC'
         }
      ],
      limit: 20
    },
    metadata: {
      display_name: 'Purchases (past 2 weeks)',
    },
    refresh_rate: 0
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

const savedQueryName = 'my-saved-query';

client
  .put({
    url: client.url('queries', 'saved', savedQueryName),
    api_key: client.masterKey(),
    params: {
      query: {
        analysisType: 'sum',
        targetProperty: 'price',
        eventCollection: 'purchases',
        timeframe: 'this_2_weeks',
        filters: [
          {
            propertyName: 'price',
            operator: 'gte',
            propertyValue: 1.00,
          },
        ],
        groupBy: [
          'platform',
        ],
        orderBy: [
          {
            propertyName: 'result',
            direction: 'DESC',
          },
        ],
        limit: 20,
      },
      metadata: {
        displayName: 'Purchases (past 2 weeks)',
      },
      refreshRate: 0,
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
saved_query_attributes = {
  refresh_rate: 0,
  query: {
    analysis_type: "sum",
    target_property: "price",
    event_collection: "purchases",
    timeframe: "this_2_weeks",
    filters: [
      {
        property_name: "price",
        operator: "gte",
        property_value: 1.00
      }
    ],
    group_by: [
        "platform"
    ],
    order_by: [
        {
            property_name: "result",
            direction: "DESC"
        }
    ],
    limit: 20
  },
  metadata: {
    display_name: "Purchases (past 2 weeks)",
  }
}
Keen.saved_queries.create("QUERY_NAME", saved_query_attributes)
saved_query_attributes = {
  "refresh_rate": 0,
  "query": {
    "analysis_type": "sum",
    "target_property": "price",
    "event_collection": "purchases",
    "timeframe": "this_2_weeks",
    "filters": [
      {
        "property_name": "price",
        "operator": "gte",
        "property_value": 1.00
      }
    ],
    "group_by": [
        "platform"
    ],
    "order_by": [
        {
            property_name: "result",
            direction: "DESC"
        }
    ],
    "limit": 20
  },
  "metadata": {
    "display_name": "Purchases (past 2 weeks)",
  }
}
keen.saved_queries.create("QUERY_NAME", saved_query_attributes)
<?php

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

$query = [
    "analysis_type" => "sum",
    "target_property" => "price",
    "event_collection" => "purchases",
    "filters" =>
        [
            [
                "property_name" => "price",
                "operator" => "gte",
                "property_value" => 1.00
            ]
        ],
    "group_by" =>
        [
            "platform"
        ],
    "order_by" =>
        [
            [
                "property_name" => "result",
                "direction" => "DESC"
            ]
        ],
    "limit" => 20,
    "timeframe" => "this_2_weeks"
];

$client->createSavedQuery(['query_name' => '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.SUM)
        .withEventCollection("purchases")
        .withTargetPropertyName("price")
        .withFilter("price", FilterOperator.GREATER_THAN_EQUAL, 1.00)
        .withTimeframe(new RelativeTimeframe("this_2_weeks"))
        .build();
        // orderBy() and limit() are not supported yet
// Currently not supported by this SDK.

Response

{
    "updated": false,
    "refresh_rate": 0,
    "created": true,
    "user_last_modified_date": "2018-12-13T14:44:56.034766+00:00",
    "last_modified_date": "2018-12-13T14:44:56.034766+00:00",
    "query_name": "QUERY_NAME",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T14:44:56.034766+00:00",
    "query": {
        "filters": [
            {
                "operator": "gte",
                "property_name": "price",
                "property_value": 1
            }
        ],
        "analysis_type": "sum",
        "timezone": null,
        "order_by": [
            {
                "direction": "DESC",
                "property_name": "result"
            }
        ],
        "group_by": [
            "platform"
        ],
        "timeframe": "this_2_weeks",
        "target_property": "price",
        "interval": null,
        "limit": 20,
        "event_collection": "purchases"
    },
    "metadata": null,
    "run_information": null
}

# 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

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 KeenAnalysis from 'keen-analysis';

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

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

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

$results = $client->getSavedQueryResults(['query_name' => '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("QUERY_NAME");
// Currently not supported by this SDK.

Response

{
    "refresh_rate": 0,
    "user_last_modified_date": "2018-12-13T15:00:48.073000+00:00",
    "last_modified_date": "2018-12-13T15:00:48.073000+00:00",
    "query_name": "QUERY_NAME",
    "result": [
        {
            "platform": "Mobile",
            "result": 1283.0400000000004
        },
        {
            "platform": "Web",
            "result": 323.7299999999999
        }
    ],
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T15:00:48.073000+00:00",
    "query": {
        "filters": [
            {
                "operator": "gte",
                "property_name": "price",
                "property_value": 1.00
            }
        ],
        "analysis_type": "sum",
        "timezone": null,
        "order_by": [
            {
                "direction": "DESC",
                "property_name": "result"
            }
        ],
        "group_by": [
            "platform"
        ],
        "timeframe": "this_2_weeks",
        "target_property": "price",
        "interval": null,
        "limit": 20,
        "event_collection": "purchases"
    },
    "metadata": null,
    "run_information": null
}

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 '{  
          "refresh_rate": 0,
          "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 KeenAnalysis from 'keen-analysis';

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

client
  .put(client.url('queries', 'saved', 'QUERY_NAME'))
  .auth(client.masterKey())
  .send({
    refresh_rate: 0,
    query: {
       analysis_type: 'count',
       event_collection: 'purchases',
       filters: [
         {
           property_name: 'price',
           operator: 'gte',
           property_value: 1.00
         }
       ],
       timeframe: 'this_1_weeks'
    },
    metadata: {
      display_name: 'Purchases (past week)',
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .put({
    url: client.url('queries', 'saved', 'QUERY_NAME'),
    api_key: client.masterKey(),
    params: {
      refreshRate: 0,
      query: {
        analysisType: 'count',
        eventCollection: 'purchases',
        filters: [
          {
            propertyName: 'price',
            operator: 'gte',
            propertyValue: 1.00,
          },
        ],
        timeframe: 'this_1_weeks',
      },
      metadata: {
        displayName: 'Purchases (past week)',
      },
    },
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
saved_query_attributes = {
  refresh_rate: 0,
  metadata: {
    display_name: "New Display Name",
  }
}
Keen.saved_queries.update("QUERY_NAME", saved_query_attributes)
"saved_query_attributes" = {
  "refresh_rate": 0,
  "metadata": {
    "display_name": "New Display Name",
  }
}
keen.saved_queries.update("QUERY_NAME", saved_query_attributes)
<?php

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

// please note that refresh_rate is currently not supported by this SDK

$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' => '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("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("QUERY_NAME", fullUpdates);

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

Response

{
    "updated": true,
    "refresh_rate": 14400,
    "created": false,
    "user_last_modified_date": "2018-12-13T15:17:08.479000+00:00",
    "last_modified_date": "2018-12-13T15:17:08.479000+00:00",
    "query_name": "QUERY_NAME",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T15:09:33.724000+00:00",
    "query": {
        "filters": [
            {
                "operator": "gte",
                "property_name": "price",
                "property_value": 1
            }
        ],
        "analysis_type": "count",
        "timezone": null,
        "order_by": null,
        "group_by": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "limit": null,
        "event_collection": "purchases"
    },
    "metadata": null,
    "run_information": null
}

HTTP Methods

Method Authentication Description
PUT Master Key Update a saved query

Optional Parameters

Parameter Description
query Parameters for the query you want to save

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 KeenAnalysis from 'keen-analysis';

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

client
  .get(client.url('queries', 'saved'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('queries', 'saved'),
    api_key: client.masterKey(),
  })
  .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

[
  {
    "refresh_rate": 14400,
    "user_last_modified_date": "2018-12-13T15:09:11.293000+00:00",
    "last_modified_date": "2018-12-13T15:10:10.461000+00:00",
    "query_name": "QUERY_NAME",
    "urls": {
      "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
      "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T15:00:48.073000+00:00",
    "query": {
      "filters": [
        {
          "operator": "gte",
          "property_name": "price",
          "property_value": 1
        }
      ],
      "analysis_type": "count",
      "timezone": null,
      "order_by": null,
      "group_by": null,
      "timeframe": "this_1_weeks",
      "interval": null,
      "limit": null,
      "event_collection": "purchases"
    },
    "run_information": {
      "last_run_date": "2018-12-13T15:10:05.005667",
      "last_run_message": "success",
      "last_run_status": 200,
      "next_run_date": "2018-12-13T19:10:05.005667"
    },
    "metadata": null
  }
]

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'

# or alternatively:

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME?api_key=access_key_with_query_definition_permitted \
    -H 'Content-Type: application/json'
// Currently not supported by this SDK.
import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

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

$results = $client->getSavedQuery(['query_name' => '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("QUERY_NAME");
// Currently not supported by this SDK.

Response

{
    "refresh_rate": 0,
    "user_last_modified_date": "2018-12-13T15:00:48.073000+00:00",
    "last_modified_date": "2018-12-13T15:00:48.073000+00:00",
    "query_name": "QUERY_NAME",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T15:00:48.073000+00:00",
    "query": {
        "filters": [
            {
                "operator": "gte",
                "property_name": "price",
                "property_value": 1.00
            }
        ],
        "analysis_type": "sum",
        "timezone": null,
        "order_by": [
            {
                "direction": "DESC",
                "property_name": "result"
            }
        ],
        "group_by": [
            "platform"
        ],
        "timeframe": "this_2_weeks",
        "target_property": "price",
        "interval": null,
        "limit": 20,
        "event_collection": "purchases"
    },
    "run_information": null,
    "metadata": null
}

HTTP Methods

Method Authentication Description
GET Master Key Get a saved query definition

Request Parameters

Parameter Description
api_key Optional alternative to an Authorization header. Must contain query_definition in the permitted property array.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

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

$client->deleteSavedQuery(['query_name' => '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("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 customer-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": 43200,
          "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 KeenAnalysis from 'keen-analysis';

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

client
  .put(client.url('queries', 'saved', 'my-saved-query'))
  .auth(client.masterKey())
  .send({
    refresh_rate: 43200
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .put({
    url: client.url('queries', 'saved', 'my-saved-query'),
    api_key: client.masterKey(),
    params: {
      refreshRate: 43200,
    },
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
# update refresh_rate in saved_query_attributes

saved_query_attributes = {
  refresh_rate: 43200,
  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)
// 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

{
    "updated": true,
    "refresh_rate": 43200,
    "created": false,
    "user_last_modified_date": "2018-12-13T15:39:32.899000+00:00",
    "last_modified_date": "2018-12-13T15:39:32.899000+00:00",
    "query_name": "QUERY_NAME",
    "urls": {
        "cached_query_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME",
        "cached_query_results_url": "/3.0/projects/PROJECT_ID/queries/saved/QUERY_NAME/result"
    },
    "created_date": "2018-12-13T15:09:33.724000+00:00",
    "query": {
        "filters": [
            {
                "operator": "gte",
                "property_name": "price",
                "property_value": 1
            }
        ],
        "analysis_type": "count",
        "timezone": null,
        "order_by": null,
        "group_by": null,
        "timeframe": "this_1_weeks",
        "interval": null,
        "limit": null,
        "event_collection": "purchases"
    },
    "metadata": null,
    "run_information": null
}

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 48 hours (172,800 seconds). If you own any specific project that requires Cached Queries to be updated more frequently, please reach out to us anytime!

Optional Parameters

Parameter Description
zero_fill It only applies to cached queries, which have the group_by clause and the interval. When zero_fill=true, then all intervals have exactly the same set of group_by keys. Those missing in a particular interval are filled with zeros. Notice that it may greatly explode the result size. Consider setting zero_fill=false especially when your result is huge. (default: true)

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 KeenAnalysis from 'keen-analysis';

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

client
  .put(client.url('datasets', 'my-first-dataset'))
  .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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .put({
    url: client.url('datasets', 'my-first-dataset'),
    api_key: client.masterKey(),
    params: {
      displayName: 'Count Daily Product Purchases Over $100 by Country',
      query: {
        analysisType: 'count',
        eventCollection: 'purchases',
        filters: [
          {
            propertyName: 'price',
            operator: 'gte',
            propertyValue: 100,
          }
        ],
        groupBy: 'ip_geo_info.country',
        interval: 'daily',
        timeframe: 'this_500_days',
      },
      indexBy: '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.
KeenProject keenProject = new KeenProject(KEEN_PROJECT_ID, KEEN_WRITE_KEY, KEEN_READ_KEY, KEEN_MASTER_KEY);

CachedDatasets cachedDatasetsClient = new KeenQueryClient
       .Builder(keenProject)
       .build()
       .getCachedDatasetsClient();

DatasetQuery query = DatasetQueryBuilder
       .aDatasetQuery()
       .withAnalysisType("count")
       .withEventCollection("purchases")
       .withFilters(singletonList(new Filter("price", FilterOperator.GREATER_THAN, "100")))
       .withGroupBy(singletonList("ip_geo_info.country"))
       .withTimeframe("this_500_days")
       .withInterval("daily")
       .build();

DatasetDefinition datasetDefinition = cachedDatasetsClient.create(
       "count-purchases-gte-100-by-country-daily",
       "Count Daily Product Purchases Over $100 by Country",
       query,
       singletonList("product.id")
);
// 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,
  "status": "Created"
}

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. This value cannot be the same as a group_by property set in the query definition. There might be up to three index_by properties (i.e. "index_by": ["product_id", "country.name"]). Index_by supported types: String, Number, Boolean, or json object.
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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('datasets', 'my-first-dataset'),
    api_key: client.masterKey(),
  })
  .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.
KeenProject keenProject = new KeenProject(KEEN_PROJECT_ID, KEEN_WRITE_KEY, KEEN_READ_KEY, KEEN_MASTER_KEY);

CachedDatasets cachedDatasetsClient = new KeenQueryClient
       .Builder(keenProject)
       .build()
       .getCachedDatasetsClient();

DatasetDefinition datasetDefinition = cachedDatasetsClient
        .getDefinition("count-purchases-gte-100-by-country-daily");
// 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,
  "status": "OK"
}

HTTP Methods

Method Authentication Description
GET Read Key Get a Cached Dataset definition

Status field in response

Value Description Result retrieval
Created The Cached Dataset is created with status “Created” Not allowed
Bootstrapping Usually within a minute from creation a Cached Dataset is picked up for processing and the “status” changes to “Bootstrapping” Not allowed
OK If bootstrapping finishes successfully. It’s now possible to retrieve results form the Cached Dataset Allowed
BootstrappingFailed If bootstrapping fails Not allowed
Warn Bootstrapping finished successfully, but encountered some failures during a subsequent Cached Dataset update Allowed, however, results may have missing or inaccurate data. See “errorMessage” to identify the root cause.

Retrieving results from a Cached Dataset

Once your dataset has finished bootstrapping (“status” has changed from Bootstrapping to OK), 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    datasetName: 'my-first-dataset',
    indexBy: '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
KeenProject keenProject = new KeenProject(KEEN_PROJECT_ID, KEEN_WRITE_KEY, KEEN_READ_KEY, KEEN_MASTER_KEY);

CachedDatasets cachedDatasetsClient = new KeenQueryClient
       .Builder(keenProject)
       .build()
       .getCachedDatasetsClient();

DatasetDefinition datasetDefinition = cachedDatasetsClient
       .getDefinition("count-purchases-gte-100-by-country-daily");

List<IntervalResultValue> results = cachedDatasetsClient.getResults(
       datasetDefinition,
       singletonMap("product.id", "some-product-id"),
       new RelativeTimeframe("this_7_days")
);
// 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 index_by value for which results will be retrieved. Simplified syntax is available for a single index_by property of type String: index_by=INDEX_BY_VALUE. If there are more than one properties or the index_by property is not a String: index_by={"property1":"string_value","property2":5.0,"property3":true}
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
zero_fill It only applies to cached datasets, which have the group_by clause and the timeframe spans more than one interval. When zero_fill=true, then all intervals have exactly the same set of group_by keys. Those missing in a particular interval are filled with zeros. Notice that it may greatly explode the result size. Consider setting zero_fill=false especially when your result is huge. (default: true)

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 KeenAnalysis from 'keen-analysis';

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

client
  .get(client.url('datasets'))
  .auth(client.readKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('datasets'),
    api_key: client.readKey(),
  })
  .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.
KeenProject keenProject = new KeenProject(KEEN_PROJECT_ID, KEEN_WRITE_KEY, KEEN_READ_KEY, KEEN_MASTER_KEY);

CachedDatasets cachedDatasetsClient = new KeenQueryClient
        .Builder(keenProject)
        .build()
        .getCachedDatasetsClient();

List<DatasetDefinition> definitions = cachedDatasetsClient.getDefinitions(10, "DATASET_NAME_0");
// 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,
    "status": "OK"
  }, {
    "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,
    "status": "OK"
  }],
  "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/DATASET_NAME \
    -H "Authorization: MASTER_KEY" \
    -X DELETE
// Currently not supported by this SDK.
import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .del({
    url: client.url('datasets', 'my-first-dataset'),
    api_key: client.masterKey(),
  })
  .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
KeenProject keenProject = new KeenProject(KEEN_PROJECT_ID, KEEN_WRITE_KEY, KEEN_READ_KEY, KEEN_MASTER_KEY);

CachedDatasets cachedDatasetsClient = new KeenQueryClient
       .Builder(keenProject)
       .build()
       .getCachedDatasetsClient();

boolean isDeleted = cachedDatasetsClient.delete("count-purchases-gte-100-by-country-daily");
// 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 KeenAnalysis from 'keen-analysis';

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

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

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

client
  .get({
    url: client.url('queries'),
    api_key: client.masterKey(),
  })
  .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 KeenAnalysis from 'keen-analysis';

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

client
  .query('extraction', {
    event_collection: 'purchases',
    timeframe: 'this_7_days'
  })
  .then(res => {
    // Handle results
  })
  .catch(err => {
    // Handle errors
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType: 'extraction',
    eventCollection: '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.
// Currently not supported by this SDK.
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.
content_type (extractions to a file only) Specifies the extraction data format. Allowed values: “text/csv” (default), “application/json”, “application/jsonstream”
content_encoding (extractions to a file only) Specifies if the extraction data should be compressed. Allowed values: “gzip”
include_metadata Specifies whether to enrich query results with execution metadata or not.

Limits

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

There are 3 limits for synchronous extractions:

  1. The maximum number of events that can be returned in a response is 100,000.
  2. The maximum size of a response is 100MB.

Requests exceeding any of these limits 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 a file, which has a higher extraction limit of 10,000,000 events.

Extract to a file

Request

$ 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .query({
    analysisType:'extraction',
    eventCollection: '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 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.

Data format

By default the result file is CSV, however, you can modify the response type by means of two request body parameters: content_type and content_encoding:

content_type content_encoding file type Description
csv Default CSV format.
application/json json Each line contains one json object with data.
application/jsonstream gzip json.gz Format described above, gzipped
text/csv gzip csv.gz CSV gzipped

Limits

The maximum number of events that can be extracted per file is 10,000,000. Requests exceeding this limit will error. The maximum extraction file size is 2GB. Requests exceeding this limit will error. If you are breaking this limit consider reducing the number of events per extraction or use compression.

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 web UI, you can utilize this feature to programmatically create Keen 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis();
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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
      url: client.url('version', 'organizations', orgId, 'projects', projectId),
      api_key: orgKey,
  })
  .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
{
    "id": "5efe28d679797411542c7fbe",
    "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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis();
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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
      url: client.url('version', 'organizations', orgId, 'projects'),
      api_key: orgKey,
  })
  .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

[
   {
        "id": "5efe28d679797411542c7fbe",
        "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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis();
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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .post({
      url: client.url('version', 'organizations', orgId, 'projects'),
      api_key: orgKey,
      params: {
        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
{
    "id": "5efe28d679797411542c7fbe",
    "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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis();
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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .post({
      url: client.url('version', 'organizations', orgId, 'projects', projectId),
      api_key: orgKey,
      params: {
        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
{
    "id": "5efe28d679797411542c7fbe",
    "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”, “query_definition”
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 resource names this key is allowed to access.
options.saved_queries.blocked A list of saved_query resource 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.

“query_definition” permitted

When “query_definition” is permitted, an access key will let you fetch a definition of a saved / cached query 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .post({
    url: client.url('projectId', 'keys'),
    api_key: client.masterKey(),
    params: {
      name: 'My Access Key',
      isActive: true,
      permitted: [ 'queries', 'cached_queries' ],
      options: {
        queries: {
          filters: [
            {
              propertyName: 'customer.id',
              operator: 'eq',
              propertyValue: 'f234124dsfb',
            }
          ]
        },
        cachedQueries: {
          allowed: [ 'my-cached-query' ],
        }
      }
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
key_body = {
  "name" => "An Example Autofill Key",
  "is_active" => true,
  "permitted" => ["writes"],
  "options" => {
    "writes" => {
      "autofill": {
        "foo": "bar"
      }
    }
  }
}

new_key = Keen.access_keys.create(key_body)
autofill_write_key = new_key["key"] # autofill_write_key will contain the new key's value
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.create_access_key(name='My Access Key', is_enabled=True, permitted=['queries', 'cached_queries'],
                         options={
                           'queries': {
                             'filters': [
                               {
                                 'property_name': 'customer.id',
                                 'operator': 'eq',
                                 'property_value': 'f234124dsfb'
                               }
                             ]
                           },
                           'cached_queries': {
                             'allowed': ['my-cached-query']
                           }
                         }
                       })
// 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

The returned list of Access Keys is paginated by default and could be controlled by two parameters page and per_page described in detail below.

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys?name=MyAccessKey
  -H "Authorization: MASTER_KEY"

# using pagination:

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys?page=1&per_page=10
  -H "Authorization: MASTER_KEY"
import KeenAnalysis from 'keen-analysis';

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

client
  .get(client.url('projectId', 'keys'))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .get({
    url: client.url('projectId', 'keys'),
    api_key: client.masterKey(),
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
Keen.access_keys.all
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.list_access_keys()
// 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": "MyAccessKey",
      "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.

Optional Query Parameters

Parameter Description
name An exact name of the Access Key to retrieve. There may be only one Access Key with the same name within a particular project.
page Needed for pagination. Defines which page of results to return and depends upon the per_page value. If not provided, the default is 0.
per_page Needed for pagination. Defines the number of results to return within a particular page. If not provided, the default is 200 which is the maximum value as well.

Get an Access Key

Request

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

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

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

client
  .get(client.url('projectId', 'keys?name=' + keyName))
  .auth(client.masterKey())
  .send()
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
import KeenAnalysis from 'keen-analysis';

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

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

client
  .get({
    url: client.url('projectId', `keys?name=${keyName}`),
    api_key: client.masterKey(),
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK
Keen.access_keys.get("key-value-here")
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.get_access_key('my-unique-key-id')
// 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

client
  .post({
    url: client.url('projectId', 'keys', keyName),
    api_key: client.masterKey(),
    params: {
      name: 'My Updated Access Key',
      isActive: true,
      permitted: [ 'queries', 'cached_queries' ],
      options: {
        queries: {
          filters: [
            {
              propertyName: 'customer.id',
              operator: 'eq',
              propertyValue: 'f234124dsfb',
            }
          ]
        },
        cachedQueries: {
          allowed: [
            'my-cached-query',
            'another-cached-query',
          ]
        }
      }
    }
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
update_body = {
  name: "New Key Name",
  is_active: false,
  permitted: ['reads']
}

Keen.access_keys.update("key-value-here", update_body)
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.update_access_key_full(access_key_id='my-unique-key-id', 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']
                                }
                              }
                            })
// 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

client
  .post({
    url: client.url('projectId', 'keys', keyName, 'revoke'),
    api_key: client.masterKey(),
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
Keen.access_keys.revoke("key-value-here")
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.revoke_access_key('my-unique-key-id')
// 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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

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

client
  .post({
    url: client.url('projectId', 'keys', keyName, 'unrevoke'),
    api_key: client.masterKey(),
  })
  .then(res => {
    // Handle response
  })
  .catch(err => {
    // Handle error
  });
// Currently not supported by this SDK.
Keen.access_keys.unrevoke("key-value-here")
from keen.client import KeenClient

client = KeenClient(
    project_id='xxxx',
    master_key='zzzz'
)

client.unrevoke_access_key('my-unique-key-id')
// 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

Deleting an Access Key

Request

$ curl https://api.keen.io/3.0/projects/PROJECT_ID/keys/CUSTOM_KEY \
  -H "Authorization: MASTER_KEY" \
  -X DELETE
// 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.
// Currently not supported by this SDK.

Response

204 - No Content

Supported HTTP Methods

Method Authentication Description
POST Master Key Delete an Access Key

Maintenance

Discovery

Request

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

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

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

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

client
  .post({
    url: client.url('version'),
    api_key: client.masterKey(),
  })
  .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 and Update

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

Tips and best practices:

  • Deletes and updates 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.
  • For deletes, be sure to pass filters as query string parameters, not as a part of a content body where they will be ignored.
  • Deleting and updating events should be done sparingly, not as a matter of routine. Running large number of deletes and updates, or using deletes as a way to update old events is an anti-pattern. Unlike writes and queries, deletes and updates are not a highly performant operations on the platform.
  • DELETE and PUT 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 or update when specifying filters is 100,000. If attempting to delete more than 100,000 events, subdivide by timeframe. Without filter specified, the limit for update and delete is set to 1,000,000 events.
  • Updates are not atomic, so concurrent query can return inconsistent results. Try to avoid sending concurrent updates on the same collection.

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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .del({
    url: client.url('events', 'my-event-stream'),
    api_key: client.masterKey(),
  })
  .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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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 = {
  url: client.url('events', 'my-event-stream'),
  api_key: client.masterKey(),
  filters: encodeURIComponent(JSON.stringify([
    {
      propertyName: 'user.id',
      operator: 'eq',
      propertyValue: '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)
  .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 KeenAnalysis from 'keen-analysis';

const client = new KeenAnalysis({
  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
  });
import KeenAnalysis from 'keen-analysis';

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

client
  .del({
    url: client.url('events', 'my-event-stream', properties, 'ip_address'),
    api_key: client.masterKey(),
  })
  .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 Project

Deletion specified project can be accomplished using our API. However, please be cautious which project you would like to delete because it’s a hard delete operation and it causes a project’s removal forever.

Request

$ curl https://api.keen.io/3.0/organizations/ORG_ID/projects/PROJECT_ID \
  -X DELETE \
  -H "Authorization: ORGANIZATION_KEY" \
  -H "Content-Type: application/json"
// 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

204 - No content

Supported HTTP Methods

Method Authentication Description
DELETE Organization Key Deletes specified project in the organization.

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.

Update Events

Request

# This example updates events related to a specific client, labelling them as spam and updating their description so they could be handled differently in other analyses:
# PUT
$ curl "https://api.keen.io/3.0/projects/${PROJECT_ID}/events/${COLLECTION_NAME} \
  -H "Authorization: ${MASTER_KEY}" \
  -X PUT \
  -d '{
        "property_updates": [
        {
          "property_name": "quantity",
          "property_value": 0
        },
        {
          "property_name": "description",
          "property_value": "Invalid event",
          "upsert_property": true
        }
      ],
      "timeframe": { 
        "start": "2020-09-13T15:00:00.000Z",
        "end": "2020-09-20T19:00:00.000Z"
      },
      "filters" : [
          {
              "property_name" : "customer.id",
              "operator" : "eq",
              "property_value" : 67kferw56lf67dd
          }
      ]
    }'
// Currently not supported by this SDK.
client = Keen::Client.new(
  project_id: PROJECT_ID,
  master_key: MASTER_KEY,
  api_url: "https://api.keen.io"
)
client.update(:collection, {
  property_updates: [
    {
      property_name: "description",
      property_value: "Invalid event"
    },
    {
      property_name: "is_spam",
      property_value: true,
      upsert_property: true
    }
  ],
  timeframe: { 
        "start": "2020-09-13T15:00:00.000Z",
        "end": "2020-09-20T19:00:00.000Z"
  },
  filters: [
    {
        property_name: "customer.id",
        operator: "eq",
        property_value: "67kferw56lf67dd"
    }
  ]
})
# 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
{
    "updated_events": 4
}

Update method can be used to update specified properties of the events falling within the timeframe and meeting filter.

Supported HTTP Methods

Method Authentication Description
PUT Master Key Update events

Request Body Parameters

Parameter Description
timeframe Limits the update operation to a specific period of time when the events occurred.
property_updates List containing JSON objects describing update definitions.
filters Optional, refines the scope of events to be included in the analysis based on event property values.

Property Update definition

Parameter Description
property_name Name of property to be updated.
property_value Value to be set for updated property.
upsert_property Boolean value, defaults to false. If true and property_name doesn’t exist within processed event, inserts property with property_value set, otherwise just update it.

Optional Query String Parameters

Parameter Description
api_key Alternative authentication method to providing an Authorization header.
include_metadata Specifies whether to enrich query results with execution metadata or not.

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 garment 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