> For the complete documentation index, see [llms.txt](https://docs.keewano.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keewano.com/connecting-to-keewano.md).

# Connecting to Keewano

Send your events once. Get answers everywhere.

## **How Keewano ingests your data**

Keewano meets your data where it already lives. Wherever your events come from — a stream, an app, or a warehouse — you send them through one of three simple interfaces: the REST API, an SDK, or bulk Import. From there, Keewano does the heavy lifting: it keeps the complete record (no sampling, ever), enriches it with context and meaning, and serves it back agent-ready through MCP, SQL, REST, BI tools, and AI agents.

The division of labor is refreshingly simple: **you connect your source to a Keewano interface, and we store, enrich, and serve the data**. There's no ETL to build or babysit, and no funnels or joins to define up front — your agents query behavior and meaning, not raw tables.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-465c56f97147633a66d009cada3250f644e558c5%2Funknown.png?alt=media)

## **Before you begin**

You'll be up and running with just an API key — create one in your Keewano settings, and you're ready to send. Here's what you'll use throughout this guide:

* **API key** — create it in your Keewano settings and send it as the k-token header (or the k-token query parameter where headers can't be set). Your project is derived from the key, so you never include a project id in the body.
* **Ingest endpoint** — `POST https://api.keewano.com/api/v1/json/in`
* **Event catalog** — built-in event types use ids `< 2000`; your own custom types use ids `>= 2000` and are registered once via `POST /json/custom`.
* **SDKs** — available for React Native / Expo, Node.js, Web (TypeScript), Python, Native Android, Native iOS, and Unity. Browse them at [github.com/Keewano](https://github.com/Keewano).
* **Import (bulk)** — \[\[ accepted file formats and trigger — separate from the REST API ]]

## **Choose your path**

Not sure where to start? Match the interface to how your data is produced:

| **If your data is…**                    | **Use…**                                                                  |
| --------------------------------------- | ------------------------------------------------------------------------- |
| Real-time from streaming infrastructure | Kafka, database change data capture (CDC), or your own backend → REST API |
| First-party applications                | Your mobile, web, or game apps → SDK                                      |
| Warehouses, lakes, and backfills        | Snowflake, BigQuery, Parquet/Iceberg, database snapshots → Import (bulk)  |

{% hint style="info" %}
**Rule of thumb**:

* Streaming sources → REST
* Warehouses and lakes → Import
* First-party apps → SDK.

Databases often use both — Import for the initial snapshot, REST for ongoing changes.
{% endhint %}

***

## **The three interfaces**

### **REST API**

Send a batch of events for a single user or install with one HTTPS call — the go-to for server-to-server and streaming integrations.

**Steps**:

1. Create your API key in Keewano settings; you'll send it as the k-token header.
2. Build a batch: choose an identity (installId or userId), set a dataSessionId and an incrementing batch number, set customEventsHash (0 if no custom events), and add an events array.
3. Each event carries a numeric eventTypeId, a timestamp (unix seconds), and a data value whose shape depends on the type. Send events in chronological order.
4. POST to /api/v1/json/in with the k-token header.
5. Handle the response: 202 means accepted for async processing (queryable shortly after). Retrying the same batch number is safe — ingestion is idempotent per (dataSessionId, batch) and never double-counts.

A complete request looks like this:

```
POST https://api.keewano.com/api/v1/json/in
k-token: <your API key>
Content-Type: application/json

{
  "dataSessionId": "a1e9c7d2-4f83-4b6e-9c1a-2d7f5b0e8a34",
  "batch": 1,
  "customEventsHash": 0,
  "installId": "f3b8d61c-9a2e-4d70-8c15-6b4e2a9f0d71",
  "sdkVersion": "server/1.0",
  "events": [
    { "eventTypeId": 3,  "timestamp": 1751932801, "data": 1751932801 },
    { "eventTypeId": 20, "timestamp": 1751932830, "data": "play_button" },
    { "eventTypeId": 32, "timestamp": 1751932860, "data": "sku_NX900" }
  ]
}
```

Response (202 — accepted for processing):

```
{ "received": 3, "accepted": 3 }
```

`received` is the number of events in the request; `accepted` is how many were taken for processing. `accepted` is 0 when the batch was a duplicate / out-of-sequence, or when every event was individually malformed. Malformed individual events are dropped rather than failing the whole batch.

**Response codes**: 202 accepted · 400 malformed body · 401 missing API key · 403 invalid API key.

**Custom events**: for your own event types (ids ≥ 2000), register a custom-events map once with `POST /json/custom` (check with `GET /json/custom`), then set `customEventsHash` to that map on each batch and give each custom event a `dataType`. Built-in events (ids < 2000) need no registration — just use `customEventsHash: 0`.

### **SDK**

Prefer to drop in a library? The Keewano SDK builds and ships these same batches for you, off the render path, and captures common events automatically after a single init call — you simply add the manual and custom events that matter to your product.

**Steps**:

1. Add the Keewano SDK for your platform — available for React Native / Expo, Node.js, Web (TypeScript), Python, Native Android, Native iOS, and Unity.
2. Initialize once with your API key (from Keewano settings).
3. Automatic tracking begins immediately; add manual or typed custom events for product-specific actions.

Find every SDK — with install and setup instructions — on GitHub at [github.com/Keewano](https://github.com/Keewano).

### **Import (bulk)**

Have history to load, or data that already sits in a warehouse or lake? Import brings it in as files, on your schedule — ideal for cloud warehouses, data lakes, and backfills.

**Steps**:

1. Export your data to files in an accepted format (\[\[ Parquet / CSV / JSON — confirm ]]).
2. Make the files available to Keewano and trigger the import (\[\[ import trigger — confirm ]]).
3. For ongoing loads, extract incrementally (by watermark, partition, or Iceberg snapshot) and chunk large backfills.

***

## **Connect your source**

Each source below connects through one of the three interfaces. The diagrams show what lives in your environment versus what Keewano provides — so it's clear exactly where your work ends and ours begins.

### **Apache Kafka**

{% hint style="info" %}
Interface: REST API | Cadence: real-time
{% endhint %}

Forward messages from your Kafka topics to the Keewano REST API as they arrive.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-21e76de09b3e4a7bffa5346e41c02080a8d31a9c%2Funknown.png?alt=media)

1. Deploy a Kafka Connect HTTP Sink connector (configuration, not code) — or a small consumer service — subscribed to the relevant topics.
2. Map each message to the batch shape (`eventTypeId`, `timestamp`, `data`, plus `installId/userId`) and POST to `/api/v1/json/in` with your `k-token`.
3. Batch by key and send in chronological order; use an incrementing `batch` number per `dataSessionId` so retries stay idempotent. Monitor consumer lag.

### **PostgreSQL**

{% hint style="info" %}
Interface: Import + REST | Cadence: snapshot once, then streaming
{% endhint %}

Backfill existing rows with a one-time Import, then stream ongoing row changes to REST via change data capture.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-add82654400f608c863d9889902af84922b72cf6%2Funknown.png?alt=media)

1. Take an initial snapshot of the tables you want and load it through Import.
2. Enable logical replication (wal\_level = logical) and capture changes with Debezium (or a logical-replication reader).
3. Forward each change to `/api/v1/json/in` (`k-token` header), mapping the row to an `eventTypeId` + `data` and the primary key to `installId/userId`, ordered by commit time.

### **Snowflake**

{% hint style="info" %}
Interface: Import (bulk) | Cadence: scheduled micro-batch
{% endhint %}

Extract new and changed rows on a schedule and bulk-import the files.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-f3a0a2c6225b8425431d2482a8f73bbf435b67f5%2Funknown.png?alt=media)

1. Use Streams + Tasks to capture new/changed rows and unload them to a stage (cloud storage) as files.
2. Bulk-import the staged files into Keewano (\[\[ import trigger ]]).
3. Advance an incremental watermark (stream offset or timestamp) each run; size the warehouse/schedule for cost vs freshness.<br>

### **Google BigQuery**

{% hint style="info" %}
Interface: Import (bulk) | Cadence: scheduled batch
{% endhint %}

Export partitions to storage (or read via the Storage Read API) and bulk-import them.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-4120bc5041b5e822ac51281f42ca175c1add53ad%2Funknown.png?alt=media)

1. Run a scheduled query / export to GCS (Avro or Parquet), or read via the BigQuery Storage Read API.
2. Bulk-import the exported files into Keewano (\[\[ import trigger ]]).
3. Use partition pruning (\_PARTITIONTIME or a watermark column) so each run reads only new data.

### **Parquet / Iceberg**

{% hint style="info" %}
Interface: Import (bulk) | Cadence: batch + incremental
{% endhint %}

Bulk-import files from object storage; use Iceberg snapshots for incremental loads.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-b7440f223266ba086f61b2b53488df666c3600a3%2Funknown.png?alt=media)

1. Point Keewano's Import at your Parquet files or Iceberg tables in object storage (S3 / GCS / ADLS).
2. For Iceberg, load new snapshots incrementally; for plain Parquet, use a partitioning/manifest convention to track what is new.
3. Provide credentials for object storage and the Iceberg catalog (\[\[ Glue / REST / Hive ]]).

### **Apps (SDK)**

{% hint style="info" %}
Interface: SDK | Cadence: real-time
{% endhint %}

Instrument your first-party apps so events flow directly from the client — the least-effort path.

![](https://3698466984-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWQi6lbHOA8tyhBBRtvuQ%2Fuploads%2Fgit-blob-281c8dca60e2279aa19aa6751a30fd15804a0974%2Funknown.png?alt=media)

1. Add the Keewano SDK for your platform (React Native / Expo, Node.js, Web TypeScript, Python, Native Android, Native iOS, Unity).
2. Initialize with your API key (from Keewano settings); automatic tracking starts immediately.
3. Report product-specific actions as manual or typed custom events.

***

## **Mapping your data to the Keewano event model**

However your data reaches Keewano, it lands in the same simple model: a batch of events for one user or install. Each event is small — a type, a time, and a value.

### **The event object**

Every entry in the batch's events array has these fields:

| **Field**       | **Required?** | **Description**                                                                                                                             |
| --------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **eventTypeId** | required      | Numeric event type. Built-in catalog types use ids < 2000; your own custom types use ids ≥ 2000 (register them once via POST /json/custom). |
| **timestamp**   | required      | Unix seconds. Send events in chronological order — the batch's time window comes from its first and last event.                             |
| **data**        | optional      | The event payload. Its shape depends on the event type's data type (see the next table).                                                    |
| **dataType**    | custom only   | Required for custom events (id ≥ 2000), ignored for built-ins. One of: none, string, uint, bool, timestamp, ushortvec2, price\_usd\_cent.   |

### **Data shapes**

The data value follows the event type's data type:

| **Data type**                 | **JSON shape**                                                                              |
| ----------------------------- | ------------------------------------------------------------------------------------------- |
| **string**                    | A string.                                                                                   |
| **uint / timestamp / ubyte**  | A non-negative integer (timestamp = unix seconds; ubyte 0–255).                             |
| **price\_usd\_cent**          | A signed integer amount, in cents.                                                          |
| **float**                     | A number.                                                                                   |
| **bool**                      | true / false.                                                                               |
| **ushortvec2**                | { "x": number, "y": number } (each 0–65535).                                                |
| **compound / custom-payload** | A fixed nested object (e.g. { exchange\_point, from:\[{name,count}], to:\[{name,count}] }). |
| **none**                      | Omit the field, or send null.                                                               |

### **Identity and batch**

A few batch-level fields tie the events to a user and keep delivery safe:

* **Identity** — each batch belongs to one user or install. Send exactly one of installId or userId. Both are strings: a UUID, or a UINT64 as a string (a full UINT64 > 2^53 is unsafe as a JSON number).
* **dataSessionId** — a UUID that identifies the session.
* **batch** — an incrementing sequence number. Ingestion is idempotent per (dataSessionId, batch), so re-sending a batch is safe.
* **customEventsHash** — 0 when the batch has no custom events; otherwise the registered custom-events map hash.
* **testUserName** — optional; flags QA traffic.

### **Common built-in event types**

| **id**  | **name**                     | **data shape**                              |
| ------- | ---------------------------- | ------------------------------------------- |
| 2       | APP\_LAUNCH                  | string                                      |
| 3       | SESSION\_START               | number (unix seconds)                       |
| 16      | SCENE\_LOADED                | string (screen/route name)                  |
| 20      | BUTTON\_CLICK                | string (button name)                        |
| 22 / 23 | WINDOW\_OPEN / WINDOW\_CLOSE | string (window name)                        |
| 32      | PURCHASE\_PRODUCT\_ID        | string (+33 price usd cents, +35 timestamp) |
| 36      | AB\_TEST\_ASSIGNMENT         | { test\_name, group }                       |
| 50      | ONBOARDING\_MILESTONE        | string (milestone name)                     |
| 57–59   | AD\_REVENUE\_\*              | timestamp / placement / usd cents           |
| 64–66   | SUBSCRIPTION\_REVENUE\_\*    | timestamp / package / usd cents             |

***

## **Validate your integration**

1. Send a small test batch to `/api/v1/json/in` (REST), fire a few events from a test build (SDK), or import a sample file (Import).
2. Confirm a `202` and check `received` vs `accepted`; remember ingestion is async, so events are queryable shortly after acceptance.
3. Verify events are attributed to the right identity and appear in chronological order, then enable the full feed.

***

## **Coming soon: reference forwarders & templates**

To make integration even faster, Keewano plans to publish ready-made reference forwarders and templates — for example a Kafka Connect HTTP Sink configuration, a Debezium recipe for Postgres CDC, and sample scheduled-import jobs for Snowflake and BigQuery. Until then, you connect your source to the interfaces above using the tools you already run — and we're here to help.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.keewano.com/connecting-to-keewano.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
